在许多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)

当前回答

解释

执行{}while(0)和if (1) {} else确保宏被扩展到只有一条指令。否则:

if (something)
  FOO(X); 

将扩大为:

if (something)
  f(X); g(X); 

g(X)将在if控制语句之外执行。当使用do {} while(0)和if (1) {} else时,可以避免这种情况。


更好的选择

使用GNU语句表达式(不是标准C的一部分),你有一个比{}while(0)和if (1) {} else更好的方法来解决这个问题,只需使用({}):

#define FOO(X) ({f(X); g(X);})

并且此语法与返回值兼容(注意do {} while(0)不兼容),例如:

return FOO("X");

其他回答

@jfm3 -你对这个问题的回答很好。你可能还想补充一点,宏习语还可以通过简单的'if'语句防止可能更危险的(因为没有错误)意外行为:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

扩展:

if (test) f(baz); g(baz);

这在语法上是正确的,因此没有编译器错误,但可能会产生意想不到的后果,即总是会调用g()。

虽然编译器应该优化掉do{…},(假);循环,还有另一个解决方案不需要这个结构。解决方案是使用逗号操作符:

#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结构。

上述答案解释了这些结构的含义,但两者之间有一个未被提及的显著差异。事实上,有一个理由更喜欢做…而如果…其他结构。

如果……Else结构的特点是它不强制你输入分号。就像下面的代码:

FOO(1)
printf("abc");

尽管我们(错误地)漏掉了分号,但代码将扩展为

if (1) { f(X); g(X); } else
printf("abc");

并且将静默地编译(尽管一些编译器可能会对不可访问的代码发出警告)。但是printf语句永远不会被执行。

做……While构造就没有这样的问题,因为While(0)后面唯一有效的标记是分号。

解释

执行{}while(0)和if (1) {} else确保宏被扩展到只有一条指令。否则:

if (something)
  FOO(X); 

将扩大为:

if (something)
  f(X); g(X); 

g(X)将在if控制语句之外执行。当使用do {} while(0)和if (1) {} else时,可以避免这种情况。


更好的选择

使用GNU语句表达式(不是标准C的一部分),你有一个比{}while(0)和if (1) {} else更好的方法来解决这个问题,只需使用({}):

#define FOO(X) ({f(X); g(X);})

并且此语法与返回值兼容(注意do {} while(0)不兼容),例如:

return FOO("X");

由于某些原因,我不能评论第一个答案……

有些人展示了带局部变量的宏,但没有人提到不能在宏中使用任何名称!它总有一天会伤害用户!为什么?因为输入参数被替换到宏模板中。在你的宏例子中,你使用了最常用的变量名i。

例如下面的宏

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

在以下函数中使用

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

该宏将不会使用some_func开头声明的预期变量i,而是在do…宏的While循环。

因此,永远不要在宏中使用通用变量名!