C多行宏:do/while(0)与作用域块

228
我看过一些多行的 C 宏,它们被包裹在 do/while(0) 循环中,例如:
#define FOO \ do { \ 这里做一些事情 \ 再做一些事情 \ } while (0)
相较于使用基本块:
#define FOO \ { \ 这里做一些事情 \ 再做一些事情 \ }
这样写有什么好处(如果有的话)?这个问题可能与以下内容重复: 当我们定义一个宏时,do while(0) 有什么用处? 为什么 C/C++ 宏中有时会出现无意义的 do/while 和 if/else 语句? do { … } while (0) 有什么用处?

5
重复问题:https://dev59.com/0XNA5IYBdhLWcg3wh-YC 和 https://dev59.com/qXVC5IYBdhLWcg3w4Vb6 和 https://dev59.com/I3VC5IYBdhLWcg3wjx1d - sth
实际上,有另一种方法可以做到正确。({ ... }) 可以像 do {} while(0) 一样完成相同的事情。参考:http://www.bruceblinn.com/linuxinfo/DoWhile.html - wuxb
3
@wuxb:({...}) 构造是GCC扩展,不被ISO标准C支持。 - Nate Eldredge
这也是CERT推荐的方法之一:https://wiki.sei.cmu.edu/confluence/display/c/PRE10-C.+Wrap+multistatement+macros+in+a+do-while+loop - JVApen
1个回答

337

Andrey Tarasevich提供了以下解释:

  1. 在Google Groups上
  2. 在bytes.com上

[对格式进行了轻微修改。方括号[]中添加了注释]。

使用'do/while'版本的整个想法是创建一个可以扩展为常规语句而不是复合语句的宏。这样做是为了使函数样式宏的使用与所有上下文中普通函数的使用方式相一致。

考虑以下代码草图:

if (<condition>)
  foo(a);
else
  bar(a);

其中foobar是普通函数。现在假设您想要用上述性质的宏[名为CALL_FUNCS]替换函数foo:

if (<condition>)
  CALL_FUNCS(a);
else
  bar(a);
现在,如果你按照第二种方法定义宏(只有 {}),代码将不再编译,因为 if 语句的“真”分支由一个复合语句表示。当你在这个复合语句后加上 ; 时,整个 if 语句就被终止了,这样就使得 else 分支变成了孤儿(因此会出现编译错误)。
解决这个问题的一种方法是记住在宏调用之后不要加上 ;
if (<condition>)
  CALL_FUNCS(a)
else
  bar(a);

这将会编译并按预期工作,但这不是统一的。更优雅的解决方案是确保宏展开为普通语句,而不是复合语句。实现这一目标的一种方法是将宏定义如下:

#define CALL_FUNCS(x) \
do { \
  func1(x); \
  func2(x); \
  func3(x); \
} while (0)

现在这段代码:

if (<condition>)
  CALL_FUNCS(a);
else
  bar(a);

这段代码可以编译而不会出现问题。

然而,请注意我的CALL_FUNCS定义和你消息中的第一个版本之间微小但重要的区别。我在} while (0)后没有放置;。在该定义结尾处放置;将立即破坏使用“do/while”的整个目的,并使该宏几乎等同于复合语句版本。

我不知道你引用原始消息中的代码作者为什么在while (0)之后加了这个;。在这种形式下,两个变体是等价的。使用“do/while”版本的整个想法是不将此最终的;包含在宏中(出于我上面解释的原因)。


63
这篇原始帖子是我在 comp.lang.c 上发的。bytes.com 显然“挪用”了 comp.lang.c 的内容,却没有提及出处。 - AnT stands with Russia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接