在 C 语言中宏定义就是简单的文本替换,它发生在预处理阶段,通过 gcc的 -E指令可以让编译器在预处理后就退出。

图 1:gcc complie
/* Convert a string to an integer. */extern int atoi (const char *__nptr)__THROW __attribute_pure__ __nonnull ((1)) __wur;
上面这个函数声明中,出现了四个宏,分别是 __THROW, __attribute_pure__,___nonnull(), __wur,它们在这里都是用来修饰这个函数 atoi的。
attribute
__THROW这个名字看上去就知道是用来抛出异常的,但是 C 语言并没有对异常有特殊的处理机制,这里实际上是为 C++ 和 C 兼容而设计的,下面是关于 __THROW的定义,这一部分全是条件编译,主要是使用 __cplusplus 判断是不是使用 C++ 编译器。
# if !defined __cplusplus \&& (__GNUC_PREREQ (3, 4) || __glibc_has_attribute (__nothrow__))# define __THROW __attribute__ ((__nothrow__ __LEAF))# define __THROWNL __attribute__ ((__nothrow__))# define __NTH(fct) __attribute__ ((__nothrow__ __LEAF)) fct# define __NTHNL(fct) __attribute__ ((__nothrow__)) fct# else# if defined __cplusplus && (__GNUC_PREREQ (2,8) || __clang_major >= 4)# if __cplusplus >= 201103L# define __THROW noexcept (true)# else# define __THROW throw ()# endif# define __THROWNL __THROW# define __NTH(fct) __LEAF_ATTR fct __THROW# define __NTHNL(fct) fct __THROW# else# define __THROW# define __THROWNL# define __NTH(fct) fct# define __NTHNL(fct) fct# endif# endif
在普通的 C 语言代码中,__THROW 又被替换为__attribute__,它是 gcc 编译器所定义的,作用是为函数,变量,类型等指定属性,这些属性通常用于编译器优化或者代码检查,所以 atoi 后面的这四个宏,最终应该都会变为 __attrubute__ 中关于函数的属性。
在 __THROW 中,出现了两个属性 __nothrow和 __LEAF(#define __LEAF, __leaf__),这两个属性在官方文档中都有介绍。其中 __nothrow比较简单, 用于告知编译器这个函数不能抛出异常。
__leaf__在文档中的介绍是这样的
Calls to external functions with this attribute must return to the current compilation unit only by return or by exception handling.
这个名字会让人误以为这是最底层的函数调用,不能再调用其他函数,其实这里只是不允许再调用同一编译单元中的其他函数,在函数中还可以调用其他编译单元中的函数。
这种宏定义都是比较简单的,还有另外一种类似于函数的宏定义,在这里就是 __nonnull ((1)),在宏定义的内部可以直接使用对应的标识符引入外界传入的参数,在定义之后可以像使用函数一样使用它们,这里最终仍然是回到 __attribute__(__nonnull__),表示该参数不能为 null。
#ifndef __attribute_nonnull__# if __GNUC_PREREQ (3,3) || __glibc_has_attribute (__nonnull__)# define __attribute_nonnull__(params) __attribute__ ((__nonnull__ params))# else# define __attribute_nonnull__(params)# endif#endif#ifndef __nonnull# define __nonnull(params) __attribute_nonnull__ (params)#endif
libc_hidden_def
接下来看一下函数的主体,这里就是调用了另一个函数 strtol。需要注意的是函数体后面的 libc_hidden_def 也是一个宏定义。
/* Convert a string to an int. */intatoi (const char *nptr){return (int) strtol (nptr, (char **) NULL, 10);}libc_hidden_def (atoi)
这些宏定义都在文件 [include/libc-symbols.h](https://sourcegraph.com/github.com/bminor/glibc/-/blob/include/libc-symbols.h?L623:3) 中,这些宏本质上都是改变符号的名字,来方便在 glibc 内部使用。
#if IS_IN (libc)// ...# define libc_hidden_def(name) hidden_def (name)#else// ...# define libc_hidden_def(name)#endif
下面是这个宏的调用链,需要注意这里的顺序为了方便阅读与定义时的顺序是倒过来的。首先 L1 中的 ## 在宏定义中是连接,在预编译的时候,参数的值会被直接替换到这里。一个 # 代表的是字符串化,也就是参数的值会变成字符串放在这里。
# define hidden_def(name) __hidden_ver1(__GI_##name, name, name);# define __hidden_ver1(local, internal, name) \__hidden_ver2 (, local, internal, name)# define __hidden_ver2(thread, local, internal, name) \extern thread __typeof (name) __EI_##name \__asm__(__hidden_asmname (#internal)); \extern thread __typeof (name) __EI_##name \__attribute__((alias (__hidden_asmname (#local)))) \__attribute_copy__ (name)# define __hidden_asmname(name) \__hidden_asmname1 (__USER_LABEL_PREFIX__, name)# define __hidden_asmname1(prefix, name) __hidden_asmname2(prefix, name)# define __hidden_asmname2(prefix, name) #prefix name
