在一些开源的库项目中,经常看到作者在定义包含大量语句的宏时,使用do{...}while(0)封装。如:
#definemacro(cond)\ do{\ if(cond)\ dosomething();\ }while(0); |
这样做的目的是什么?
首先看一个例子,定义一个宏,其中包括与上例相同的条件语句,但不使用封装
#definemacro1(cond)\ if(cond)\ dosomething(); |
然后在如下的场景中使用宏:
//code-1 if(cond1) macro1(cond2) else dosomething(); |
按照c/c++标准规定,else与最近的if语句匹配,将宏展开后,如下:
//code-2 if(cond1) if(cond2) dosomething(); else dosomething(); |
恩恩,恩?!
不对啊,大哥,我好像要得不是这个啊。到这里估计有一些朋友还没有反应过来,让我们返回去看code-1。实际上我们在code-1是想让else匹配外面那个if的,然而当预编译后成了code-2,else就变成和里面的if匹配了。正是因为编写者将多条代码用一句宏代表了,让调用者产生了宏函数只是一句代码的错觉,特别当宏不是调用者写的时候,很容易忽略这个问题。而且最可怕是,编译器不会报错,甚至程序运行后也可能不会报错,但就是通不过测试,而且还要加上宏函数在调试上的困难...还是别这么写代码了吧...
那么宏函数这种模式就一无是处了么?其实不然,这种模式最大的优点是执行效率高,为什么这么说,因为宏函数的调用不会为cpu带来函数跳转的开销,同时提高代码复用率,当然也会带来诸如难调试,可读性差等问题,建议非高手不为。
话题扯回来,为了杜绝上述二义性,有两种解决思路:
1)使用{...}包裹语句块
如上述场景下,使用大括号封装:
#definemacro1(cond)\ {\ if(cond)\ dosomething();\ } |
是的,这样包装语句块也不会造成二义性,并且使用大括号包装语句还能制造一片局部作用域,实现一些py交易。但是这样做也有一个问题,就是分号";"问题。
如下:
#define AB2 { a; b; } if(cond) AB2; else ... |
很普通对不对,然而编译报错,因为{...}后面除了class{...};编译器不会报错(且是必须的),函数中是不允许这种语法的...还是那个原因,调用这个宏的人不一定知道这个宏的实现啊...
那么还剩下一种方法,那就是用do{...}while(0)。
2)使用do{...}while(0)包裹
#definemacro1(cond)\ do{\ if(cond)\ dosomething();\ }while(0); |
优点:
1>独立作用域,语法单元,不会和上下文混淆
2>没有分号的问题
3>循环实际上不执行,且大多数编译器都会做优化,不会影响程序性能。