为什么要在宏中使用看似毫无意义的do-while和if-else语句?
在许多C / C ++宏中,我看到的宏代码包裹在看起来毫无意义的" do while"循环中。这是例子。
#define FOO(X) do { f(X); g(X); } while (0) #define FOO(X) if (1) { f(X); g(X); } else
我看不到"一会儿做什么"在做什么。为什么不没有它就写这个呢?
#define FOO(X) f(X); g(X)
解决方案
do ... while
和if ... else
可以使之
宏后面的分号始终表示相同的意思。比方说你
有类似第二个宏的内容。
#define BAR(X) f(x); g(x)
现在,如果要在if ... else
语句中使用BAR(X);
,并且if语句的主体没有用大括号括起来,那么我们会感到非常惊讶。
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)
BarBAR
的以上版本将上述代码扩展为以下代码,从语法上讲这是正确的。
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预处理程序时,这是他们担心的事情。
@ jfm3我们对这个问题有很好的答案。我们可能还想补充一下,宏惯用法还可以通过简单的'if'语句防止可能更危险的(因为没有错误)意外行为:
#define FOO(x) f(x); g(x) if (test) FOO( baz);
扩展为:
if (test) f(baz); g(baz);
从语法上讲这是正确的,因此没有编译器错误,但是可能总是会调用g(),这可能是意料之外的结果。
宏是预处理器将在真实代码中放入的复制/粘贴的文本;宏的作者希望替换将产生有效的代码。
要成功,有三个好的"技巧":
帮助宏表现得像真正的代码
普通代码通常以分号结尾。用户是否应该查看不需要的代码...
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) ;
产生一个有效的代码2
如果宏是这样的:
#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. }
我不认为有人提到它,所以考虑一下
while(i<100) FOO(i++);
将被翻译成
while(i<100) do { f(i++); g(i++); } while (0)
注意宏如何对" i ++"进行两次评估。这可能会导致一些有趣的错误。
预计编译器会优化do {...} while(false);循环,但是还有另一种不需要该构造的解决方案。解决方案是使用逗号运算符:
#define FOO(X) (f(X),g(X))
或者更奇怪的是:
#define FOO(X) g((f(X),(X)))
虽然这在单独的指令下可以很好地工作,但是在将变量作为#define的一部分进行构造和使用的情况下,则不能使用:
#define FOO(X) (int s=5,f((X)+s),g((X)+s))
这样做将被迫使用do / while构造。