很多初学者,以及有一定工作经验的朋友都不知道这个“do{...}while(0U)”的作用和意义,甚至觉得这样写的代码复杂且没有意义。
相信看过STM32的HAL库源码和Linux内核源码的朋友,都知道源码里面有许多do{...}while(0U)的宏定义语句。除了用于宏定义之外,do{...}while(0U)还有很多作用。
2.1 确保宏定义正确展开
Google的Robert Love(先前从事Linux内核开发)解释到:
do{...}while(0U)在C中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。
简单理解就是:使用do{...}while(0U)构造后的宏定义不受使用的大括号、分号等的影响,而总会按照你期望的意图运行。
例如:
#define foo(x) bar(x); baz(x)
当如下调用:
foo(wolf);
则会被展开为:
bar(wolf); baz(wolf);
看来是没有问题。但要是结合if语句调用:
if(!value)
foo(wolf);
则会被展开为:
if(!value)
bar(wolf);
baz(wolf);
这显然就违背我们期望的效果了。但如果我们使用do{...}while(0U)来重新定义宏:
#define foo(x) do {bar(x); baz(x);}while(0U)
则展开的效果为:
if(!value)
do {
bar(x);
baz(x);
}while(0U)
2.2 实现局部作用域
我们知道宏定义只是做一个标识符和字符串的替换,尽管宏定义的形式可以类似函数,但是它实际并不具备与函数类似的局部作用域。
我们当然可以通过使用大括号的形式(如:#define func(x) {...}),来实现局部作用域,但是这样会带来新的麻烦:
#define swap(a, b) {a = a + b; b = a - b; a = a -b}
int main(void)
{
int a = 1, b = 2;
if(1)
swap(a, b);
else
a = b = 0;
return 0;
}
上面代码咋一看没有问题,但是对其进行宏展开后如下:
int main(void)
{
int a = 1, b = 2;
if(1)
{
a = a + b;
b = a - b;
a = a - b;
};
else
a = b = 0;
return 0;
}
这下问题就明显了:在if后的代码后面多出了一个‘;’,这会引发编译错误。使用该宏定义时不在后面加‘;’可以解决这个问题,但这显然不符合我们的编码习惯,且要求宏编写者和使用者必须使用统一的标准。而在宏定义中使用do{...}while(0U)就可以解决这个问题:
#define swap(a, b) do{ \
a = a + b; \
b = a - b; \
a = a - b; \
}while(0U)
int main(void)
{
int a = 1, b = 2;
if(1)
swap(a, b);
else
a = b = 0;
return 0;
}
展开后的效果为:
int main(void)
{
int a = 1, b = 2;
if(1)
do{
a = a + b;
b = a - b;
a = a - b;
}while(0U);
else
a = b = 0;
return 0;
}
这样我们就可以放心地在宏定义后面使用分号而不会造成问题了。
2.3 避免使用goto语句
在C/C++语言程序中,我们可能会发生错误以后做一些特殊处理(比如资源释放),如果顺利执行则直接退出,我们常使用goto来实现:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ret = -1;
ret = func1();
if(0 != ret)
goto failed;
ret = func2();
if(0 != ret)
goto failed;
ret = func3();
if(0 != ret)
goto failed;
return 0;
failed:
do_err();
}
但是,不建议在程序中大量使用goto。虽然使用goto语句可以解决代码冗余,也能提高程序的灵活性与简洁性,但这样也会使程序结构变得混乱不易维护(我们更看重的是可维护性)。我们可以使用do{...}while(0U)来代替goto实现相同的功能:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ret = -1;
do {
ret = func1();
if(0 != ret)
break;
ret = func2();
if(0 != ret)
break;
ret = func3();
if(0 != ret)
break;
return 0;
}while(0U);
do_err();
}
2.4 避免空声明在编译时出现警告
在Linux内核源代码中,经常看到如下宏定义以避免在编译时出现警告:
#define FOO do { }while(0U)
网友评论