为什么有时候在C/C++宏里,有毫无意义的do/while和if/else块?

在很多C/C++宏里我发现有很多貌似无任何实际意义的do/while或者if/else代码块。比如:

  
#define FUNC(X) do { f(X); g(X); } while (0)
#define FUNC(X) if (1) { f(X); g(X); } else

不明白do/while在这里有什么用?为什么不直接像下面这样写:

  
#define FUNC(X) f(X); g(X)

不是更简洁吗?

评论 (1)链接2011-11-04 
  • 0 支持
    看提的问题,"很多"宏里面(题目是"有时候").
    你确定你见过这个宏超过两次以上,并且自己举的例子没有错误? 看楼下那么多牵强的分析真是让人蛋疼啊.
    – bithaha 2012-06-12

这么做在我看来有如下几点考虑:

  1. do/while 保证了这段代码只执行一次而无需关心do/while内部代码。
  2. 组织更高效的代码,在do/while包含中的代码,在编译的以后被作为一段完整的代码块。
  3. 还有就是保证语句的完整执行和语法错误,例如f(x)或者g(x)代码中有if分支语句,要是没有do-while把多条语句组织成一个代码块,则程序的运行结果就不正确,甚至不能编译。
  4. 通过内敛宏定义的这段代码,编译后省去了函数指针的调用,本身作为代码块节省了空 间。

对于非求值的宏,即代码块宏,用do {....}while(0)包围代码块,防止产生意外代码。
倘若使用{}包围代码,可能出现错误代码。比如:

  
 #define boo()    \
{ \
.... \
}

在if语句中使用它,
if(x)
b00();
else
...

然而上述代码不能如预期的工作,展开后为
if(x)
{
....
}
;
else
...

可见多出了一个';',但使用do...while(0)不会出现此问题。
do...while(0)似乎多了一个不必要的循环,不过编译器优化时会识别。
do...while(0)另一用处在于避免goto语句的使用,比如:
do {
if(...)
break;/*不满足要求跳出*/
.....

if(...)
break;/*不满足要求跳出*/
.....

return;/*成功*/
}while(0)
..../*不满足后的处理*/
刘锡涛
刘锡涛
281
编辑于 2012-03-02
该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (6)链接 • 2011-11-04
  • 0 支持
    1.带不带do while(0)不都是执行一次,除非while(100);我愚昧了,还是理解不了。
    3.f和g如果是函数,管它们内部有什么都不会互相影响,这是基本的模块性;
    如果是宏,有这种相互作用,另当别论,但是带不带do while套子都不会出错,而且,do while本身是语句(复合语句),不会嵌套在表达式中,怎么放置也不会出错。
    – magic 2011-11-04
  • 0 支持
    do while(0)只是比开闭花括号多这俩部分,也一样会有展开后的替换问题(多出个分号)。 – magic 2011-11-04
  • 0 支持
    这分析真是见了鬼了,乍一看挺有道理.其实都是错的,因为题目就是错的. – bithaha 2012-06-12
  • 0 支持
    如果写成这样呢 #define FUNC(X) { f(X); g(X); }
    不就是保证完整吗?这样也可以吧!
    – wayne173 2012-08-17
显示更多隐藏的评论

do { f(X); g(X); } while (0)

这种写法确实是为了在某些情况下(调用函数出错,不能继续往下执行)
可以用break跳出来,这种做法叫做“统一出口”,类似于用goto,
目前来讲很多程序员或者公司都保留goto的这个作为统一出口的习惯用法。
而且非常有用。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2011-11-04
  
#define FUNC(X) if (1) { f(X); g(X); } else

这个是为了保证语言执行的完整性,把这个执行过程展开一下你就明白了

比如:

  
if(true)
FUNC(x);
else
dosomething();

宏展开后成为如下代码:

  
if (true)
f(x); g(x);
else
dosomething();
这时执行完f(x)后,会自动跳出if(true),不执行g(x)了
所以执行到这里,就很容易明白了,这个宏这是为了保证可以连续执行
{ f(X); g(X); }

#define FUNC(X) do { f(X); g(X); } while (0)

这个有三方面的原因
1.单纯为了避免使用goto语句
2.如果用if代替do{..}while(0),因为if分支后有两个语句,else分支没有对应的if,编译错误,假设没有else, 第二个语句无论if测试是否通过,会永远执行。

以上二点,所以才会有了#define FUNC(X) do { f(X); g(X); } while (0)

一言以蔽之,就是#define FUNC(X) if (1) { f(X); g(X); } else有些情况下无法满足,所以用#define FUNC(X) do { f(X); g(X); } while (0)进行了扩展
马瑜
编辑于 2011-11-04
该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (1)链接 • 2011-11-04
  • 0 支持
    第一个带if的FUNC的风格可不好,而且宏体直接花括号就行,没必要加if else,可能你给的例子不好吧。 – magic 2011-11-04

如果f和g是函数,这俩例子确实没必要,顶多加个花括号吧;
如果f和g是宏,得看它们的内部处理了,比如对do,某些条件直接break就能跳过后续的代码;
通常见到的这种应用都不是这么简单的形式吧。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2011-11-04

非常赞同@magic的答案,如果f,g是宏定义,那么这种定义多半是为了break跳出方便。另外补充一点对于#define FUNC(X) if (1) { f(X); g(X); } else
这个定义,在使用时如果后面不加“;”有屏蔽下一条语句的影响,编译不会报错,所以这是一个
比较危险的定义,慎用!。

小飞
小飞
6842
编辑于 2011-11-17
该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (2)链接 • 2011-11-04
  • 0 支持
    后面的注意事项说的不错,而且,宏定义本身要慎用,表面上看来,这个非完整的宏定义风格就不好,容易误用。
    好的代码通常也会比较好看的。
    – magic 2011-11-04
  • 0 支持
    认同俺的观点,就给个支持吧,呵呵。 – magic 2011-11-04

大家说得都挺好,宏只是简单的代码替换,加入这些是为了防止宏替换代码时对上下文产生副作用。对宏的使用看似简单,但真正用好挺不容易的。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-02-09

楼上说得很到位,其实c\C++中的宏非常强大,可以实现很多高级功能,比如(#),(#@),(##)的使用。你看看MSDN上的#define讲解,我相信很多c\C++程序员没去MSDN上查过#define。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2011-11-06

一般情况下来说是为了防止宏的副作用,但是在不同的具体应用环境下可能有不同,可以把应用的环境贴出来看看

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2011-11-04

宏定义最基本的需要把所有定义的东西用括号括起来吧?

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-08-01

你是在MFC下看到的代码吧,WINDOWS里经常使用这样的方式定义宏变量,这样可以有效的防治函数宏产生的副作用,同时,对于这样的宏,可以放心的调用。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-10-16
德问是一个专业的编程问答社区,请 登录注册 后再提交答案