在许多 C / C ++ 宏中,我看到了包裹在看起来毫无意义的do while
循环中的宏代码。这是例子。
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
我看不到什么do while
做。为什么不没有它就写这个呢?
#define FOO(X) f(X); g(X)
do ... while
和if ... else
存在于此,因此宏后的分号始终表示同一意思。假设您有类似第二个宏的内容。
#define BAR(X) f(x); g(x)
现在,如果您要使用BAR(X);
在if ... else
语句中, if ... else
语句的主体没有用大括号括起来,您会感到非常惊讶。
if (corge)
BAR(corge);
else
gralt();
上面的代码将扩展为
if (corge)
f(corge); g(corge);
else
gralt();
这在语法上是错误的,因为 else 不再与 if 关联。在宏中用大括号将内容包装起来没有帮助,因为大括号后的分号在语法上是不正确的。
if (corge)
{f(corge); g(corge);};
else
gralt();
有两种解决问题的方法。第一种是使用逗号对宏内的语句进行排序,而不会使其失去像表达式一样的功能。
#define BAR(X) f(X), g(X)
上面的 bar BAR
版本将上面的代码扩展为以下代码,这在语法上是正确的。
if (corge)
f(corge), g(corge);
else
gralt();
如果您有一个更复杂的代码体需要进入其自己的块中,例如声明局部变量,则这不起作用,而不是f(X)
。在最一般的情况下,解决方案是使用诸如do ... while
使宏成为包含分号且不会引起混淆的单个语句。
#define BAR(X) do { \
int i = f(X); \
if (i > 4) g(i); \
} while (0)
您不必使用do ... while
,你可以编造一些与if ... else
为好,尽管当if ... else
扩大了内部的if ... else
,就会造成 “ 晃来晃去别的 ”,这将使现有悬而未决的其他问题更加难以发现,如以下代码所示。
if (corge)
if (1) { f(corge); g(corge); } else;
else
gralt();
关键是在悬空分号错误的情况下用完分号。当然,在这一点上可以(也许应该)认为,将BAR
声明为实际函数而不是宏会更好。
总而言之, do ... while
可以解决 C 预处理程序的缺点。当那些 C 风格指南告诉您解雇 C 预处理程序时,这是他们担心的事情。
宏是预处理器将在真实代码中放入的复制 / 粘贴的文本;宏的作者希望替换将产生有效的代码。
要成功,有三个好的 “技巧”:
普通代码通常以分号结尾。用户是否应该查看不需要的代码...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
这意味着如果缺少分号,则用户希望编译器产生错误。
但是真正真正的好理由是,宏的作者有时可能需要用真正的功能替换宏(也许是内联的)。因此,宏实际上应该像一个宏。
因此,我们应该有一个需要分号的宏。
如 jfm3 的答案所示,有时该宏包含多个指令。而且,如果在 if 语句中使用了宏,则将出现问题:
if(bIsOk)
MY_MACRO(42) ;
该宏可以扩展为:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
无论bIsOk
的值如何,都将执行g
函数。
这意味着我们必须将范围添加到宏:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
如果宏类似于:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
以下代码可能会出现另一个问题:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
因为它将扩展为:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
当然,此代码不会编译。因此,该解决方案再次使用范围:
#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
void doSomething()
{
int i = 25 ;
{ int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}
该代码再次正常运行。
有一种 C / C ++ 习惯用法可以产生这种效果:do / while 循环:
do
{
// code
}
while(false) ;
do / while 可以创建范围,从而封装宏的代码,最后需要使用分号,从而扩展为需要一个代码的代码。
奖金?
C ++ 编译器将优化 do / while 循环,因为在编译时就知道其后置条件为 false。这意味着一个宏像:
#define MY_MACRO(x) \
do \
{ \
const int i = x + 1 ; \
f(i) ; g(i) ; \
} \
while(false)
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
MY_MACRO(42) ;
// Etc.
}
将正确扩展为
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
do
{
const int i = 42 + 1 ; // was MY_MACRO(42) ;
f(i) ; g(i) ;
}
while(false) ;
// Etc.
}
然后被编译和优化为
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
@ jfm3 - 您对这个问题有很好的答案。您可能还想补充一下,宏惯用语还可以通过简单的'if' 语句防止可能更危险的(因为没有错误)意外行为:
#define FOO(x) f(x); g(x)
if (test) FOO( baz);
扩展为:
if (test) f(baz); g(baz);
从语法上讲这是正确的,因此没有编译器错误,但是可能会意外地导致总是调用 g()。