为什么C语言中的某些函数有下划线前缀?

17

我最近开始学习C语言中的网络编程,看到了一些以下划线开头的函数- 如 _function() - 这是什么意思?我还见过这个:

 struct sockaddr_in  {  

__SOCKADDR_COMMON (sin_);  

 in_port_t sin_port;    

 struct in_addr sin_addr;    

 unsigned char sin_zero[sizeof (struct sockaddr) - 

 __SOCKADDR_COMMON_SIZE -  

sizeof (in_port_t) -         

sizeof (struct in_addr)];  

};

这段代码的这些部分是什么意思:

__SOCKADDR_COMMON (sin_);

unsigned char sin_zero[sizeof (struct sockaddr) - 

 __SOCKADDR_COMMON_SIZE -  

sizeof (in_port_t) -         

sizeof (struct in_addr)];

请参阅在C语言中双下划线(__const)的含义是什么?,该链接引用了C标准关于以下划线开头的名称的规定。 - Jonathan Leffler
2个回答

27

下划线前缀保留用于编译器和标准库使用的函数和类型。标准库可以自由地使用这些名称,因为它们永远不会与正确的用户程序冲突。

另一方面,你不能定义以下划线开头的名称。

那么,这就是该规则的要点。实际规则是:

  • 你不能在全局范围内定义任何以下划线开头的标识符,因为这些可能会与隐藏(私有)库定义冲突。所以这在你的代码中是无效的:

    #ifndef _my_header_h_
    #define _my_header_h_ // wrong
    int _x; // wrong
    float _my_function(void); // wrong
    #endif
    

    但这是有效的:

    #ifndef my_header_h
    #define my_header_h // ok
    int x; // ok
    float my_function(void) { // ok
        int _x = 3; // ok in function
    }
    struct my_struct {
        int _x; // ok inside structure
    };
    #endif
    
  • 你不能在任何作用域中定义以两个下划线开头或者一个下划线后面跟着一个大写字母的标识符。因此,以下代码是无效的:

  • struct my_struct {
        int _Field; // Wrong!
        int __field; // Wrong!
    };
    void my_function(void) {
        int _X; // Wrong!
        int __y; // Wrong!
    }
    

    但这没问题:

    struct my_struct {
        int _field; // okay
    };
    void my_function(void) {
        int _x; // okay
    }
    
    其实还有一些规则,为了让事情变得更复杂,但以上规则最常被违反且最易记住。

2
@CraigEstey __x_X 可能是宏。但这是程序员自己选择是否冒这个风险。99.44%的这类名称永远不会发生冲突,如果真的发生了冲突,祝你好运解决问题。考虑到在代码中引入错误非常容易,为什么你要做任何你知道可能会引入无法解决的错误的事情呢? - Andrew Henle
3
你很幸运。我只使用C语言编程了大约33年,因为内部代码使用下划线开头的名称与系统函数产生冲突,所以遇到了一些棘手的问题。如果你的代码定义了int _bind(int x, char y);,而系统定义并调用了char *_bind(void *p, char *a, int f);或类似的函数,那么情况会变得非常糟糕 - 系统库代码最终会调用你的_bind()而不是预期的函数。虽然这已经是好几年前的事情了(实际上是上个千年),但是我还记得这件事和一些类似的情况。 - Jonathan Leffler
3
“_”是POSIX库常规约定。请阅读C标准文档中的“7.1.3保留标识符”章节,链接为http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf。其中写道:“所有以下划线和大写字母或另一个下划线开头的标识符始终保留供任何用途使用。”以及“所有以下划线开头的标识符始终保留作为普通和标签名称空间中具有文件范围的标识符使用。”从逻辑上讲,你的观点就像是“我被鲨鱼包围着游泳,身上沾满了鲜血,但并没有被吃掉”的说法一样。你提到了35年? - Andrew Henle
2
@CraigEstey:这就是我提到“另一个千年”的原因——现在的问题通常不同,但名称的选择仍然可能会引起问题。这是少数几个(我认为不到半打)与前导下划线名称干扰的案例之一。(我要补充的是,将_bind()作为名称的本地使用不是我的选择,在移植到新平台之前它并没有引起问题。)我遇到过更多的问题是在类型上添加了 POSIX 保留的 _t 后缀。理论上,您必须积极激活 POSIX 功能——与自动执行的 -std=gnu11 不同。_ […继续…]_ - Jonathan Leffler
2
POSIX规则的存在是为了防止你违反它们并受到伤害时无法申诉。在很大程度上,C语言规则也是如此。它们还指导系统开发人员——如果他们遵循规则,那么如果用户也遵循规则,他们就不会伤害到用户。棘手的问题是,新手看着系统头文件,认为“哦,这一定是我编写头文件的方式”,然后立即打破了所有旨在防止用户和系统提供者互相伤害的规则。最终,这是一个教育问题。SO可以提供帮助。 - Jonathan Leffler
显示剩余10条评论

8
Leading underscores通常表示以下三种情况之一:
  1. 定义不属于C标准,因此不具有可移植性
  2. 定义是库或编译器内部的,并且不应从外部使用
  3. 定义不应轻易使用,因为它意味着某些风险或需要额外知识的必要配置。
在这种情况下,__SOCKADDR_COMMON是(2):一个内部定义,属于struct sockaddr_in类型,这是您通常从用户空间访问的类型。

_Bool_Complex是语言的一部分,并以_开头。 - 12431234123412341234123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接