do{...}while(0)在宏定义中的应用
今天在看linux内核中链表操作的接口的时候,碰到这样一个宏定义:
1 | // INIT_LIST_HEAD宏用于运行时初始化链表 |
不太明白这里的do{...}while(0)
是个什么操作,看起来似乎没有什么用——因为这里while(0)
显然并没有起到循环的效果。后来查了下资料(网址在这),才知道在Linux内核和其它一些著名的C库中有许多使用do{…}while(0)的宏定义,也大概明白了这么写的作用。
第一个理由:空的 statement 会让编译器发出警告,所以会看到有些宏定义是这样的:
#define FOO do{ }while(0)
。(暂时没看到这样的宏 = =。 先记录一下第二个理由:它提供了一个 block 用于声明局部变量。可能你会想到不使用
do{...}while(0)
而简单地使用一对大括号{...}
。这样有缺陷,具体看下一条。第三个理由:让你能够声明复杂的宏定义。想象一下一个宏定义如下:
1
2
3现在这么使用它:
1
2if (blah == 2)
FOO(blah);宏本质上就是文本替换,所以它实际上:
1
2
3if (blah == 2)
printf("arg is %d\n", blah);
do_something_useful(blah); // 不论 blah为何止,都会执行这条所以显然,这可能带来用于预期之外的效果。而如果用了
do{...}while(0)
,就会是这样的:1
2
3
4
5
6if (blah == 2)
do {
printf("arg is %d\n", blah);
do_something_useful(blah);
} while (0);
// OK可能有人会想,既然需要一个 block ,那么加个大括号不就好了?好的,假如说有这样一个宏定义:
1
2
3
4
5
6
7// 交换两个值
if (x > y)
exch(x, y); // 看起来似乎没问题
else
do_something();很显然,这个相当于这样:
1
2
3
4
5
6
7
8
9
10if (x > y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
}
; // 注意这里!!
else // 语法错误
do_something();问题就出现在那个
;
。当然,你可以选择当初写下each(x, y)
的时候在这行末尾不加分号,但是(我觉得)这实在显得太奇怪了。用了
do{...} while (0)
就没有这个问题:1
2
3
4
5
6
7
8
9if (x > y)
do {
int tmp;
tmp = x;
x = y;
y = tmp;
} while (0); // Ok
else
do_something();
总结:Linux和其它代码库里的宏都用 do{...}while (0)
来包围执行逻辑,因为它能确保宏的行为总是相同的,而不管在调用代码中使用了多少分号和大括号。
(一点吐槽:感觉就是给C语言的宏擦屁股的?还有就是 if/else
即使只有一条语句也加大括号真是个好习惯 = =。