对于一个C programmer,宏命令再熟悉不过了,宏的功能非常强大,只要使用正确它会极大地减轻你的工作量。
可是,只要一不小心,宏也是非常危险的,它会让你陷入彻底的纠结和疯狂。在很多C程序中,你会见到很多看起来不是很直接的特殊的宏定义,如:
#define __set_task_state(tsk, state_value) \
do { (tsk)->state = (state_value); } while (0)
这种写法在Linux内核和C库源码中非常常见,那么这种宏定义的用意究竟何在呢?目前就职于Google linux内核开发部的
Robert Love 给出了答案:
do {...} while (0)是C语言中唯一的一种构造(construct)来让宏命令始终如一的以一种正确的方式工作,即无论如何使用宏命令(特别强调的一种情况是用在没有花括号的if语句后面),宏命令后加封号所起的作用始终都是一样的。
举例如下:
#define foo(x) bar(x); baz(x)
后面的程序可能会这样调用该宏命令:
foo(wolf);
编译器预处理为:
bar(wolf); baz(wolf);
这个是预期的编译预处理结果,但接下来如果这样调用宏命令:
if (!feral)
foo(wolf);
该结果可能会并不是你所期许的那样:
if (!feral)
bar(wolf);
baz(wolf);
写一个在所有情况下都正确地达到你期许目的的multistatement 宏是不可能的。离开do/while(0)结构,让宏定义表现的像一个函数是不可能的。
如果我们用do/while(0)重新定义上面的宏,
#define foo(x) do { bar(x); baz(x); } while (0)
现在,该宏定义功能上等价于前者。do确保花括号里的内在逻辑得到执行,while(0)确认该逻辑得到执行并且只执行一次,同上调用,编译预处理结果为:
if (!feral)
do { bar(wolf); baz(wolf); } while (0);
语义等价如下:
if (!feral) {
bar(wolf);
baz(wolf);
}
说到这里,你可能会困惑为什么在宏定义外加层花括号达不到目的?为什么还必须要
do/while(0)结构。
我们仅仅用花括号,定义如下:
#define foo(x) { bar(x); baz(x); }
这样的定义对上面的调用是没有问题的,但如果调用如下:
if (!feral)
foo(wolf);
else
bin(wolf);
预编译结果为:
if (!feral) {
bar(wolf);
baz(wolf);
};
else
bin(wolf);
这是语法错误。
综上所述,在Linux和其他代码库中,宏内的逻辑都包含在do/while(0)结构中,确保在调用代码不管如何使用分号和花括号,宏在程序中的表现总是相同的。
—————————————————————————————————————————————————————————————————————————————
update:
编译自:http://www.pixelstech.net/article/1350871981-Significance-and-use-of-do%7B-%7Dwhile%280%29-
- Help define complex macro to avoid error
- Avoid warning caused by macro
由于内核的不同体系架构约束,空的宏定义可能会经常使用,编译的时候,空的宏定义会产生warning,为了避免这种warning,可以使用do{...}while(0)来定义空的宏。
#define EMPTYMICRO do{}while(0)
- Avoid using goto to control program workflow
在一些函数中,return语句前需要处理一些工作,如释放之前用malloc分配的空间。goto语句可能是一个简单的方法:
int foo()
{
somestruct* ptr = malloc(...);
dosomething...;
if(error)
{
goto END;
}
dosomething...;
if(error)
{
goto END;
}
dosomething...;
END:
free(ptr);
return 0;
}
由于包含goto语句的程序可读性较差,因此很多人并不推荐使用。因此,我们使用do{...}while(0)来实现上述函数的功能:
int foo()
{
somestruct* ptr = malloc(...);
do{
dosomething...;
if(error)
{
break;
}
dosomething...;
if(error)
{
break;
}
dosomething...;
}while(0);
free(ptr);
return 0;
}
这里我们使用do{...}while(0)来包含函数的主体部分,并使用break来代替goto
- Define a single function block to complete complex operations