c - C/C 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

我看不到 do while 正在做什么。 为什么不直接写这个?


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

时间:

do.. . whileif.. . else 是为了使你的宏后面的分号总是意味着相同的东西。 假设你有类似于第二个宏的东西。


#define BAR(X) f(x); g(x)

现在如果你打算用 BAR(X); 一个的,其中阀体 if.. . else 语句如果语句中并没有包裹在花括号,你肯定会得到一个坏的惊喜。


if (corge)
 BAR(corge);
else
 gralt();

上代码将扩展到


if (corge)
 f(corge); g(corge);
else
 gralt();

语法不正确,因为另一个不再与。 在宏中用大括号来换行没有帮助,因为大括号后面的分号在语法上是不正确的。


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();

如果你有一个更复杂的代码体需要在它自己的块中,比如声明局部变量,那么这不起作用。 在最常见的情况下,解决方案是使用像 do.. . while 这样的方法,使宏成为一个带有分号的单语句,而不需要混淆。


#define BAR(X) do { 
 int i = f(X); 
 if (i> 4) g(i); 
} while (0)

你不必使用 do.. . while,你也可以使用 if.. . else 编写一些东西,尽管 if.. . elseif.. . else 内部扩展,这将导致一个"悬空否则"的问题,如下面的代码所示。


if (corge)
 if (1) { f(corge); g(corge); } else;
else
 gralt();

要点是在有悬空分号的上下文中使用分号。 当然,在这一点上,更确切地讲来声明它( 并且可能应该) 多也辩称 BAR 作为一个实际的函数,而不是一个宏。

总的来说,do.. . while 是为了解决C 预处理器的缺点。 当这些C 风格的指南告诉你放置C 预处理器的时候,这就是他们所担心的。

宏是pre-processor将放入真实代码中的复制/粘贴的文本;宏的作者希望替换能产生有效的代码。

所以有三个好的"提示"可以成功

帮助宏的行为像真正的代码

普通代码通常由semi-colon结束。 用户查看代码不需要一个。。


doSomething(1) ;
DO_SOMETHING_ELSE(2)//<== Hey? What's this?
doSomethingElseAgain(3) ;

这意味着如果没有 semi-colon,用户期望编译器产生一个错误。

但真正的真正原因是,在某些时候,宏的作者可能需要用一个真正的函数( 也许内联) 替换宏。 这个宏应该真的执行普通的有。

所以我们应该有一个需要semi-colon的宏。

生成有效的代码

如jfm3的回答所示,宏包含多个指令。 如果宏在一个临时政府语句中使用,这将有问题:


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 = x + 1 ; f(i) ; } ;//was MY_MACRO(32) ;
}

代码再次正确运行。

组合 semi-colon + 作用域效果?

有一个 C/C++ 习惯用法产生这种效果: 很累。。危难循环:


do
{
//code
}
while(false) ;

这两者都可以创建一个作用域,因此封装了宏的代码并需要一个 semi-colon,因此扩展到需要一个的代码。

奖金?

C++ 编译器将优化 do/危难循环,因为它的post-condition在编译时是假的。 这意味着像这样的宏:


#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 web - 你对这个问题有一个很好的回答。 使用简单'中频'statements,你可能还想补充一点,宏的习语也阻止了可能更加危险( 因为没有错误) 意外 behavior:


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

if (test) FOO( baz);

扩展到:


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

在语法上是正确的,因此没有编译器错误,但有可能会导致 g() 总是被调用的意外结果。

虽然编译器需要优化 do {.. . } while(false); 循环,还有另一个不需要构造的解决方案。 解决方案是使用逗号运算符:


#define FOO(X) (f(X),g(X))

或者更多 exotically:


#define FOO(X) g((f(X),(X)))

虽然这在单独的指令中可以很好地工作,但在变量构造和使用为 #define的一部分时,它将不起作用:


#define FOO(X) (int s=5,f((X)+s),g((X)+s))

有了这个,将强制使用 do/while构造。

上面的答案解释了这些构造的含义,但在没有提到的两个方面有显著差异。 事实上,有一个理由让 do.. . while 成为 if.. . else 构造。

if.. . else 构造是,它不力的问题,你可以把分号。 在这里代码中:


FOO(1)
printf("abc");

尽管我们省略了分号( 按错误),代码将扩展到


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

并且将悄悄地编译( 尽管某些编译器可能会发出一个无法到达的代码警告) 。 但是 printf 语句永远不会被执行。

do.. . while 构造没有这样的问题,因为 while(0) 之后唯一有效的标记是分号。

在上面的答案后面写下了更多的内容,但是我建议通过查看编译器的预处理输出来研究宏替换。 用于 gcc

 
gcc -E file.c

 

请注意这也将转储来自你的源的所有 #includes,因此删除 #include的( 或者使用 -nostdinc 标志) 有助于保持输出的简短,并且可以轻松地看到宏替换。

尽管关于这个主题并不特别具体,我还是想添加 @John的答案。 假设你有以下宏


#define square(x) (x*x)

然后在代码中执行 square (2+2),然后它将扩展到 (2+2*2+2),这是不正确的。 所以在'x'周围的一个额外的括号应该被给出。 如下所示


#define square(x) ((x)*(x))

这将得到正确的替换 ((2+2)*(2+2))

由于某些原因,我无法对第一个答案进行评论。。

有些人显示了带有局部变量的宏,但是没有人提到你不能在宏中使用任何名称 ! 它会在某天吞噬用户 ! 为什么因为输入参数被替换为宏模板。 在你的宏示例中,你可能使用了最常用的variabled名称:

例如当下列宏


#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);
}

在做的了.,宏不会使用预期的变量i,也就是开头声明的局部变量,也就是说 declared. some_func,但 宏的循环。

因此,不要在宏中使用公共变量名 !

我认为它没有被提及,所以考虑一下这个


while(i<100)
 FOO(i++);

将被转换成


while(i<100)
 do { f(i++); g(i++); } while (0)

注意如何通过宏两次计算 i++ 。 这会导致一些有趣的错误。

Jens gustedt P99预处理器库 ( 是的,这种事情的存在让我的头脑blew了) 在 ! if(1) {.. . } else 通过定义以下内容,以一种很小但重要的方式构造:


#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

它的原因是,与 do {.. . } while(0) 构造,breakcontinue 仍然在给定的块内工作,但是如果在宏调用之后省略了分号,((void)0) 会产生语法错误,否则会跳过下一个块。 ( 这里实际上没有"正在挂起"问题,因为 else 绑定到最近的if,即宏中的。)

如果你对使用C 预处理器安全地完成more-or-less的种类感兴趣,请查阅该库。

好于 do {} while (0)if (1) {} else 你可以简单地使用 ({}):


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

这里语法与返回值兼容( do {} while (0) 't ),如:

 
return FOO("X");

 
...