C/C++中的特殊宏

在阅读VTK和QT的源码时,遇到了类似于Python中特殊变量形式的宏,如__LINE____FILE__,这两个宏的用途是作为函数参数返回调用行数和文件名。一时惊奇,原来C里面也有这个内置宏,而且貌似在各大库的Debug模块中都经常被用到。此外也碰到了变长的宏的用法,于是查了一下标准中对宏的描述,做一下笔记,对宏代码很有帮助~

文中点击标题即可转到GNU标准的页面。

Stringizing (字符串化)

在宏定义中可以将传入的参数原封不动地变成字符串常量插入代码中,使用的方法是利用#运算符。例如下面的宏

1
2
3
4
5
#define WARN_IF(EXPR) \
do { if (EXPR) \
fprintf (stderr, "Warning: " #EXPR "\n"); } \
while (0)
WARN_IF (x == 0);

其中#EXPR便是字符串化的参数,在编译时会变成

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

会输出Warning: x == 0。这一特性可以使得使用宏定义的时候同时输出参数名字或者表达式,便于进行记录。此时若x本身也是宏的话在#EXPR中不会展开。如果想让宏x#EXPR中也展开的话,需要再用另一个宏把这个宏包起来即可。

Concatenation (符号连接)

有时候向宏内传入的参数不完全是你想要的参数,或者希望通过一个参数展开成多个变量的时候,就可以使用符号连接的宏,使用方法是利用##运算符。例如假设有一个储存命名函数的结构体

1
2
3
4
5
struct command
{
char *name;
void (*function) (void);
};

使用下面的宏可以简洁方便地定义多个结构体

1
2
3
4
5
6
7
#define COMMAND(NAME)  { #NAME, NAME ## _command }

struct command commands[] =
{
COMMAND (quit),
COMMAND (help),
};

其中NAME ## _command的作用就是在NAME展开后在末尾加上_command,避免直接连接会导致宏无法被识别的现象。展开后得到

1
2
3
4
5
struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
};

Variadic Macros (可变参数的)

可变参数的用法与普通代码中的可变参数用法是一致的,即通过对最后一个参数进行特殊声明来让这个宏可以接受变长的参数。举例如下:

1
2
#define eprintf(…) fprintf (stderr, __VA_ARGS__)
#define eprintf(args…) fprintf (stderr, args)

这两个宏的展开效果是一样的,如果在代码中插入

1
eprintf ("%s:%d: ", input_file, lineno)

则会展开成

1
fprintf (stderr, "%s:%d: ", input_file, lineno)

从上面的代码可以看出,如果使用...来表示变长参数,那么在宏定义中就用预定义宏变量__VA_ARGS__来代表这些参数,如果是在某一个参数名的后面加上...,那么就是用将这个参数变成变长参数。

另外,变长参数之前可以有普通的参数,如

1
#define eprintf(format, …) fprintf (stderr, format, __VA_ARGS__)

不过需要注意的是,在标准C中这样的情况下变长参数至少需要输入一个参数,否则在转义时参数末尾会多一个逗号。即eprintf("success!\n", );会变成fprintf(stderr, "success!\n", );。在GNU CPP中这个问题可以通过在__VA_ARGS__前面加上##符号来解决。

Predefined Macros (预定义的宏)

在标准中有提供一些内置的宏,可以给调试提供很多方便~

  • __FILE__:当前文件的路径名
  • __LINE__:调用处的行号
  • __func__(C99)/__FUNCTION__(GCC):调用处所属的函数名
  • __DATE__:处理器上当前的日期
  • __TIME__:处理器上当前的时间
  • __STDC_VERSION__:C标准的版本,例如C11标准下会展开成201103
  • __cplusplus:在C++编译器时会被定义,展开结果同__STDC_VERSION__
    其他还有很多的不在标准中的预定义的宏,具体可以查看编译器的说明。其中GNU C的预定义宏可以参考Common Predefined Macros

Directives Within Macro Arguments (在宏参数里修改宏)

GNU编译器还提供了在展开宏的参数时修改宏的功能。。。这个功能非常少用,先举个病态的例子:

1
2
3
4
5
#define f(x) x x
f (1
#undef f
#define f 2
f)

这一段语句展开后得到的结果是1 2 1 2,这就是在参数中修改宏。但是这个宏非常影响阅读,太tricky了,最好还是别用。

Shoot me some coffee money XD
0%