对于C语言的初学者,可能只是简单的用宏去定义一些变量或者简单函数。然而宏的功能远远不是这么简单的,在一些比较著名的开源软件中,都能看到大量的宏定义。本文结合GCC文档,对宏的一些扩展用法进行了总结,由于个人水平原因,可能并不是十分全面,欢迎各路大神补充。
部分样例来自于GCC文档: https://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros
# & ##
#是将参数转换成字符串,在参数两边自动加上双引号。1
2
3
typestr(int) ==>"int"
##用于连接串1
2
3
connect(hello, world) ==> func_hello_world
这两个宏经常用来生成代码,比如我们要定义一个命令列表。1
2
3
4
5
6
7
8struct command {
char *name;
void (*func)(void*);
};
struct command commands[] = {
{"quit", quit_command},
{"help", help_command}
}
列表的名字和回调函数之间有一定的联系,我们可以用宏做如下替换。1
2
3
4
5
struct command commands[] = {
COMMAND(quit),
COMMAND(help)
}
这里还有一个小技巧,就是用宏#来查看宏生成的结果,这在测试一些复杂宏时十分有用。xstr宏的目的是先让参数展开,之后再通过str宏转化成字符串。1
2
3
4
5
6
7
8
xstr(foo) => xstr(4)
=> str(4)
=> "4"
str(foo) => "foo"
可变参数列表
宏可以接收可变参数列表,我们可以用这个特性来封装一个DEBUG宏用于输出调试信息。1
2
3
4
5
int x = 10;
DEBUG("%d", x)
===> printf("DEBUG: %d\n", x)
但是这个宏是有一些问题的,下面的代码会因为宏展开后多一个逗号而无法通过编译。1
DEBUG("hello") ===> printf("hello",)
解决的办法就是在__VA_ARGS__前加上##,这两个#会在__VA_ARGS__为空时消除前面的逗号。1
2
DEBUG("hello") ===> printf("hello")
代码块替换
在程序中,我们通常用{}来包含一个代码块,但在宏中这样使用会出现一些问题,比如如下一个宏1
2
3
4
5#define SKIP_SPACES(p, limit) \
{ char *lim = (limit); \
while (p < lim) { \
if (*p++ != ' ') { \
p--; break; }}}
在如下代码中展开,就会因为在else前多了一个分号而无法通过编译。1
2
3if (*p != 0)
SKIP_SPACES (p, lim);
else ...
解决方法就是用do{}while(0)来包含代码块,这里的分号就不会产生编译错误。1
2
3
4
5
6#define SKIP_SPACES(p, limit) \
do { char *lim = (limit); \
while (p < lim) { \
if (*p++ != ' ') { \
p--; break; }}} \
while (0)
返回值
有时候一个宏包含若干条语句,而我们又希望这个宏最后能够返回一个值,这时就可以用({..})来包含一段代码,这段代码的最后一行会作为整个代码段的值。下面使用求较小值的宏来举例。
很多C程序中都会使用下面这个宏来得到最小值1
这个宏的如果使用不当,会严重影响程序效率,比如下面这种情况就会两次运行foo函数。1
2next = min (x + y, foo (z)); ==>
next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));
而正确的写法就是使用代码块先计算出X和Y的值,最后对计算出的值进行比较。宏中typeof是GUN C的一个扩展,用于得到参数的类型。1
2
3
4
5
6
7
8
9
10
11#define min(X, Y) \
({ typeof(X) x_ = (X); \
typeof(Y) y_ = (Y); \
(x_ < y_) ? x_ : y_; })
//这里假设x,y以及foo(z)的返回值都是int类型
min (x + y, foo (z)) ===>
({ int x_ = x + y;
int y_ = foo(z);
(x_ < y_) ? x_ : y_;
})
这个宏的最后一条语句就是用于返回整个代码块的执行结果。
嵌套定义
可以将宏名作为另一个宏的参数,然后在宏中调用,类似于回调函数的效果。1
2
3
4
5
6#define TWICE(x) ((x) * 2)
#define ADDONE(x) ((x) + 1)
#define NEWSIZE(x, func_expand) (x = func_expand(x))
NEWSIZE(x, TWICE) ==> (x = ((x) * 2))
NEWSIZE(x, ADDONE) ==> (x = ((x) + 1))
内置宏
C中提供了一些内置的宏,GNU C中也提供了很多十分有用的内置宏,需要注意并不是所有的编译器都支持这些宏。这里列举几个比较有用的内置宏,更多的宏可以在GNU C文档中找到。1
2
3
4
5__FILE__ 当前所在文件
__LINE__ 当前代码所在行
__DATA__ 当前日期(格式 Feb 12 1996)
__TIME__ 当前时间(格式 23:59:01)
__COUNTER__ 累加器,从0开始,每次调用后加1