在嵌入式开发的过程中,我们经常可以在一些优秀开源代码的头文件里发现一些宏定义使用了do {} while(0)语句,也许你会疑惑do {} while(0)中的代码不就是只执行一次吗,为什么还要多此一举使用do {} while(0)循环结构去包裹呢?实际上,do {} while(0)的作用很大,下面可以看几个例子。
一、定义复杂宏避免逻辑或编译错误
假如你定义了一个宏,这个宏的作用为连续调用2个函数,宏定义如下:
#define DOSOMETHING() func1(); func2()
但使用时可能会出现以下这样的情况:
展开前:
if(judge == TRUE)
DOSOMETHING;
展开后:
if(judge == TRUE)
func1();
func2();
可以看到,由于没有加上括号,func2是必会执行的,已经引起了逻辑错误;如果这个if还有else等分支的话,会出现编译错误。为了避免这个错误,也许你会给宏加上括号,就像下面这样:
#define DOSOMETHING() { func1(); func2();}
使用时再次展开宏:
展开前:
if(judge == TRUE)
DOSOMETHING;
展开后
if(judge == TRUE)
{
func1();
func2();
};
可以看到,由于调用DOSOMETHING宏时,后面有个“;”号,所以展开后最后多了一个“;”号,显然如果这个符号是多余的,虽然有些编译器可以忽略多余的“;”,但如果这个if后面有个else语句,也会引起编译的异常。也许你会说,最后的“;”是多余的,那我调用的时候不加“;”不就可以了吗,就像下面这样。
if(judge == TRUE)
DOSOMETHING
当然这是行得通的, 但是在每个语句后面加分号是一种约定俗成的习惯,应当极力避免这种使用方式,否则阅读代码将是一件痛苦的事情!这时候do {} while(0)就派上用场了!我们可以用do {} while(0)重新定义DOSOMETHING宏:
#define DOSOMETHING() do{ func1(); func2();} while(0)
再次展开宏,就可以看到,使用上就毫无问题了! do {} while(0)真香啊!
展开前:
if(judge == TRUE)
DOSOMETHING;
展开后:
if(judge == TRUE)
do{
func1();
func2();
}while(0);
二、代替goto语句完成函数退出逻辑
虽然尽量不使用goto是C语言开发中不成文的规定,但实际上我们经常使用goto语句来完成一些清理工作:
int func()
{
bool error = false;
void *ptr = malloc(1024);
error = dosomething1();
if(error)
goto exit;
error = dosomething2();
if(error)
goto exit;
exit:
free(ptr);
return 0;
}
我们可以使用do {} while(0)结构来代替这种goto的用法,避免goto语句的使用。
int func()
{
bool error = false;
void *ptr = malloc(1024);
do{
error = dosomething1();
if(error)
break;
error = dosomething2();
if(error)
break;
}while(0);
free(ptr);
return 0;
}