在 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. */
int
atoi (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