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

未命名绘图-第 3 页.drawio.png
图 1:gcc complie

  1. /* Convert a string to an integer. */
  2. extern int atoi (const char *__nptr)
  3. __THROW __attribute_pure__ __nonnull ((1)) __wur;

上面这个函数声明中,出现了四个宏,分别是 __THROW, __attribute_pure__,___nonnull(), __wur,它们在这里都是用来修饰这个函数 atoi的。

attribute

__THROW这个名字看上去就知道是用来抛出异常的,但是 C 语言并没有对异常有特殊的处理机制,这里实际上是为 C++ 和 C 兼容而设计的,下面是关于 __THROW的定义,这一部分全是条件编译,主要是使用 __cplusplus 判断是不是使用 C++ 编译器。

  1. # if !defined __cplusplus \
  2. && (__GNUC_PREREQ (3, 4) || __glibc_has_attribute (__nothrow__))
  3. # define __THROW __attribute__ ((__nothrow__ __LEAF))
  4. # define __THROWNL __attribute__ ((__nothrow__))
  5. # define __NTH(fct) __attribute__ ((__nothrow__ __LEAF)) fct
  6. # define __NTHNL(fct) __attribute__ ((__nothrow__)) fct
  7. # else
  8. # if defined __cplusplus && (__GNUC_PREREQ (2,8) || __clang_major >= 4)
  9. # if __cplusplus >= 201103L
  10. # define __THROW noexcept (true)
  11. # else
  12. # define __THROW throw ()
  13. # endif
  14. # define __THROWNL __THROW
  15. # define __NTH(fct) __LEAF_ATTR fct __THROW
  16. # define __NTHNL(fct) fct __THROW
  17. # else
  18. # define __THROW
  19. # define __THROWNL
  20. # define __NTH(fct) fct
  21. # define __NTHNL(fct) fct
  22. # endif
  23. # 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

  1. #ifndef __attribute_nonnull__
  2. # if __GNUC_PREREQ (3,3) || __glibc_has_attribute (__nonnull__)
  3. # define __attribute_nonnull__(params) __attribute__ ((__nonnull__ params))
  4. # else
  5. # define __attribute_nonnull__(params)
  6. # endif
  7. #endif
  8. #ifndef __nonnull
  9. # define __nonnull(params) __attribute_nonnull__ (params)
  10. #endif

libc_hidden_def

接下来看一下函数的主体,这里就是调用了另一个函数 strtol。需要注意的是函数体后面的 libc_hidden_def 也是一个宏定义。

  1. /* Convert a string to an int. */
  2. int
  3. atoi (const char *nptr)
  4. {
  5. return (int) strtol (nptr, (char **) NULL, 10);
  6. }
  7. libc_hidden_def (atoi)

这些宏定义都在文件 [include/libc-symbols.h](https://sourcegraph.com/github.com/bminor/glibc/-/blob/include/libc-symbols.h?L623:3) 中,这些宏本质上都是改变符号的名字,来方便在 glibc 内部使用。

  1. #if IS_IN (libc)
  2. // ...
  3. # define libc_hidden_def(name) hidden_def (name)
  4. #else
  5. // ...
  6. # define libc_hidden_def(name)
  7. #endif

下面是这个宏的调用链,需要注意这里的顺序为了方便阅读与定义时的顺序是倒过来的。首先 L1 中的 ## 在宏定义中是连接,在预编译的时候,参数的值会被直接替换到这里。一个 # 代表的是字符串化,也就是参数的值会变成字符串放在这里。

  1. # define hidden_def(name) __hidden_ver1(__GI_##name, name, name);
  2. # define __hidden_ver1(local, internal, name) \
  3. __hidden_ver2 (, local, internal, name)
  4. # define __hidden_ver2(thread, local, internal, name) \
  5. extern thread __typeof (name) __EI_##name \
  6. __asm__(__hidden_asmname (#internal)); \
  7. extern thread __typeof (name) __EI_##name \
  8. __attribute__((alias (__hidden_asmname (#local)))) \
  9. __attribute_copy__ (name)
  10. # define __hidden_asmname(name) \
  11. __hidden_asmname1 (__USER_LABEL_PREFIX__, name)
  12. # define __hidden_asmname1(prefix, name) __hidden_asmname2(prefix, name)
  13. # define __hidden_asmname2(prefix, name) #prefix name