宏 do{}while(0)
作者:
| 更新日期:看库函数的时候,经常看到 把语句放到 do{}while(0) 中,于是查了一下资料。
本文首发于公众号:天空的代码世界,微信号:tiankonguse
心中的疑惑
在 c 的库函数中会经常看到下面的语句
#define FOO(X) do { something;} while (0)
#define FOO(X) if (1) { something; } else
浅层原因
对于第一个,还可以解释通。
一般比较复杂的宏会执行多条语句,为了保持逻辑的正确性,需要使用大括号把宏里面的语句全部抱起来,比如
#define FOO(X) { something; }
这样可以避免的问题是宏用在在了 if() else 中,然后由于看到 if 中是”一条语句”,于是没有加括号。
比如:
#define FOO(X,y) x++;y--;
if(x > y)
FOO(x,y)
else
FOO(y,x)
这个坑很多人都会遇到的,即使工作了,在公司里也有很多人会遇到的。
至少在我实习的时候,我的导师就遇到了这个问题。我让他先把所有的if 语句加上大括号,结果之前发现的问题不见了。
追踪 if 里面的语句,发现是个宏,而且宏里只是简单地多条语句,于是宏里的第一条总是正确的,后面的不管条件是否满足都是执行,坑爹呀。
另一方面,如果宏里面定义了变量,不用大括号包起来,还可能遇到重定义变量的错误。
真正的原因
使用大括号避免了if没加括号的问题,但是我们一般调用一个宏的函数后习惯加个分号,比如
#define FOO(X,y) { x++;y--;}
if(x > y)
FOO(x,y);
else
FOO(y,x);
悲剧是竟然编译不通过。
编码习惯和编码规范都摆在那里:每条语句后面都有一个分号。
难道对于宏,我们不加分号,这样将会显得格格不入了。
但是加了分号,我们那种使用大括号的方案也失效了。
这时我们就需要一个有大括号(形成代码块)且以分号结尾的宏了。
此时再看看文章开头介绍的那两个疑问的宏
#define FOO(X) do { something;} while (0)
#define FOO(X) if (1) { something; } else
if(x > y)
FOO(x);
else
FOO(y);
这时,我们的代码没有任何副作用。
回顾副作用
第一个原因是宏里面有多个语句,在没加大括号的if语句中将编译不通过,有时宏里面有定义变量时还可能遇到重定义的编译错误。
第一个解决方案:加一个大括号。
加了大括号后,发现一个奇怪的问题:我们调用宏时不能加分号。
于是第二个原因出来了:对于加了大括号的宏,在没加大括号的if语句中依旧可能会遇到编译不通过的情况。
于是就出现了 do{}while(0) 的奇葩宏定义了。
终极解决方案
发现宏都是因为在if语句里出错的,原因是if语句没有使用大括号。
当然,在所有需要加大括号,而你由于看到只有一条语句而没有加大括号的代码中,都可能遇到这个问题。
所以我们要养成良好的编码习惯,所有的if,while,for等语句都加上大括号,即使只有一条语句也要加上大括号。
if语句加上大括号还可以避免 dangling else 问题。
官方的解决方案
由于宏定义出的问题有很多,gcc 加了一个 Statement-Exprs 的语法。
#define FOO(X,y) ({ x++;y--;})
if(x > y)
FOO(x,y);
else
FOO(y,x);
就是对宏语句加个大括号,然后大括号外加个小括号。
官方有这么一句话:A compound statement enclosed in parentheses may appear as an expression in GNU C。
大概意思是混合的语句使用括号包起来后可以作为一个表达式。
do-while的好处
难道 do-while 只有这么一个好处吗?
不是的,还有其他好处。
避免宏的某些错误
见上文
避免使用 goto 语句
goto 一般用于调到结尾,但是编码规范中说了禁止使用 goto 语句。
看看下面的 goto 使用,如果不适用 goto 怎么实现呢.
void foo(){
if(error){
goto END;
}
dosomething...;
if(error){
goto END;
}
dosomething...;
END:
dosomething...;
return 0;
}
就是使用只执行一次的循环来代替 goto.
int foo(){
do{
if(error) {
break;
}
dosomething...;
if(error){
break;
}
dosomething...;
}while(0);
dosomething...;
return 0;
}
代替空的宏语句
#define EMPTYMICRO do{}while(0)
如果是空的宏,会有 warnning 的。
参考资料
本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。