GCC如何在编译错误中包含宏的值?

引子

曾经排查过一个由于编译环境差异出现的CPU使用率持续100%的BUG, 而差异就是select(2)的文件描述符操作宏所依赖的FD_SETSIZE太小了, 导致每次事件循环select都立马返回,但是FD_ISSET判断又没有可处理的句柄, 最终出现死循环。

那么,为了避免后续再出现同样的问题,有必要在预处理阶段判断FD_SETSIZE与期望值是否相符, 不相符就给出编译错误提示,提示目前此宏的值,以及调整到期望值再进行编译。

大致就是这样子的:

#if FD_SETSIZE != 12345
#error "please set the FD_SETSIZE macro to 12345"
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#pragma message "current value is: " STR(FD_SETSIZE)
#endif

当编译程序时,发现系统头文件的FD_SETSIZE值不对, 就会报错以及输出信息:

please set the FD_SETSIZE macro to 12345
current value is: 1024

此处利用了GCC预处理器的Stringification特性, 最终把FD_SETSIZE对应的值拼接到一个完整的字符串中。 预处理器的替换步骤如下(假设FD_SETSIZE是1024):

STR(FD_SETSIZE) => STR(1024)
    => STR_HELPER(1024)
    => "1024"

Stringification

有时我们在定义宏时,需要在宏扩展中把宏转为其对应的字符串, 而不是作简单地替换。(从这个角度,可以把stringification翻译成“字符串化”?) 比如有时候需要把源码中的WARN_IF (x == 0);替换成:

do { if (x == 0)
    fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);

那就需要用到此特性(摘自[2]):

#define WARN_IF(EXP) \
    do { if (EXP) \
         fprintf (stderr, "Warning: " #EXP "\n"); } \
    while (0)

关于stringification,几点说明:

  • 只有宏参数才支持。

    比如直接使用#FD_SETSIZE编译会出错,必须定义一个宏,把FD_SETSIZE作为参数传入方能起作用。

  • 预处理器处理时只会把宏的参数替换成字符串,不会拼接相邻的字符串(这是C编译器的工作)。

  • 宏参数字符串如果包含连续多个空格,仅保留一个空格;开头和结尾的空白符会被删除掉。

  • 预处理器会妥善处理字符串的转义问题,确保格式正确。

    例如,p = "foo\n";会被转义为"p = \"foo\\n\";"

  • 无法把宏参数转化为单个字符。

GCC官方关于预处理器的stringification特性,见[2]。

参考

[1] c - How do I show the value of a #define at compile time in gcc - Stack Overflow

[2] Stringification - The C Preprocessor

[3] Diagnostic Pragmas - Using the GNU Compiler Collection (GCC)

social