jfm*_*fm3 758 c c++ c++-faq c-preprocessor
在许多C/C++宏中,我看到宏的代码包含在看似无意义的do while循环中.这是一些例子.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Run Code Online (Sandbox Code Playgroud)
我看不出它do while在做什么.为什么不在没有它的情况下写这个?
#define FOO(X) f(X); g(X)
Run Code Online (Sandbox Code Playgroud)
jfm*_*fm3 797
该do ... while和if ... else在那里让这个后您的宏分号总是意味着同样的事情.假设你有类似第二个宏的东西.
#define BAR(X) f(x); g(x)
Run Code Online (Sandbox Code Playgroud)
现在,如果你要BAR(X);在一个if ... else语句中使用if语句的主体没有用大括号括起来,你会得到一个不好的惊喜.
if (corge)
BAR(corge);
else
gralt();
Run Code Online (Sandbox Code Playgroud)
上面的代码将扩展为
if (corge)
f(corge); g(corge);
else
gralt();
Run Code Online (Sandbox Code Playgroud)
这在语法上是不正确的,因为else不再与if相关联.在宏中用大括号括起来是没有用的,因为大括号后面的分号在语法上是不正确的.
if (corge)
{f(corge); g(corge);};
else
gralt();
Run Code Online (Sandbox Code Playgroud)
有两种方法可以解决问题.第一种方法是使用逗号对宏中的语句进行排序,而不会使其具有表达式的能力.
#define BAR(X) f(X), g(X)
Run Code Online (Sandbox Code Playgroud)
上面的bar版本BAR将上面的代码扩展为以下代码,这在语法上是正确的.
if (corge)
f(corge), g(corge);
else
gralt();
Run Code Online (Sandbox Code Playgroud)
如果不是f(X)你有一个更复杂的代码体,需要进入它自己的块,比如声明局部变量,这就行不通了.在最一般的情况下,解决方案是使用类似的东西do ... while使宏成为一个单独的语句,分号不会混淆.
#define BAR(X) do { \
int i = f(X); \
if (i > 4) g(i); \
} while (0)
Run Code Online (Sandbox Code Playgroud)
你不必使用do ... while,你也可以做一些东西if ... else,虽然当它if ... else内部扩展时会if ... else导致" 悬挂其他 ",这可能使现有悬挂的其他问题更难找到,如下面的代码.
if (corge)
if (1) { f(corge); g(corge); } else;
else
gralt();
Run Code Online (Sandbox Code Playgroud)
关键是在悬挂分号错误的情况下用掉分号.当然,在这一点上它可能(并且可能应该)被认为最好将其声明BAR为实际函数,而不是宏.
总之,do ... while可以解决C预处理器的缺点.当那些C风格指南告诉你裁掉C预处理器时,这就是他们担心的事情.
pae*_*bal 147
宏是复制/粘贴的文本,预处理器将放入正版代码中; 宏的作者希望替换产生有效的代码.
有三个好的"提示"可以成功:
正常代码通常以分号结束.如果用户查看不需要的代码......
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
Run Code Online (Sandbox Code Playgroud)
这意味着如果没有分号,用户希望编译器产生错误.
但真正正确的理由是,在某些时候,宏的作者可能需要用真正的函数(也许是内联函数)替换宏.所以宏应该真的像一个.
所以我们应该有一个需要分号的宏.
如jfm3的答案所示,有时宏包含多条指令.如果宏在if语句中使用,这将是有问题的:
if(bIsOk)
MY_MACRO(42) ;
Run Code Online (Sandbox Code Playgroud)
此宏可以扩展为:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
Run Code Online (Sandbox Code Playgroud)
g无论值如何,都将执行该功能bIsOk.
这意味着我们必须向宏添加一个范围:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Run Code Online (Sandbox Code Playgroud)
如果宏是这样的:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Run Code Online (Sandbox Code Playgroud)
我们可能在以下代码中遇到另一个问题:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Run Code Online (Sandbox Code Playgroud)
因为它会扩展为:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Run Code Online (Sandbox Code Playgroud)
当然,这段代码不会编译.所以,再一次,解决方案是使用范围:
#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) ;
}
Run Code Online (Sandbox Code Playgroud)
代码再次正常运行.
有一个产生这种效果的C/C++习语:do/while循环:
do
{
// code
}
while(false) ;
Run Code Online (Sandbox Code Playgroud)
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.
}
Run Code Online (Sandbox Code Playgroud)
将正确扩展为
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.
}
Run Code Online (Sandbox Code Playgroud)
然后编译和优化
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
Run Code Online (Sandbox Code Playgroud)
Mic*_*urr 51
@ jfm3 - 你对这个问题有一个很好的答案.您可能还想补充一点,宏语法也可以使用简单的"if"语句防止可能更危险(因为没有错误)的意外行为:
#define FOO(x) f(x); g(x)
if (test) FOO( baz);
Run Code Online (Sandbox Code Playgroud)
扩展为:
if (test) f(baz); g(baz);
Run Code Online (Sandbox Code Playgroud)
这在语法上是正确的,所以没有编译器错误,但可能有意外的结果,g()将始终被调用.
ybu*_*ill 23
上述答案解释了这些结构的含义,但两者之间存在显着差异,未提及.其实,还有一个原因,更喜欢do ... while到if ... else结构.
if ... else构造的问题是它不会强迫你输出分号.喜欢这段代码:
FOO(1)
printf("abc");
Run Code Online (Sandbox Code Playgroud)
虽然我们遗漏了分号(错误地),但代码将扩展为
if (1) { f(X); g(X); } else
printf("abc");
Run Code Online (Sandbox Code Playgroud)
并将静默编译(虽然一些编译器可能会发出无法访问代码的警告).但该printf声明永远不会被执行.
do ... while构造没有这样的问题,因为之后唯一有效的标记while(0)是分号.
Mar*_*ius 16
虽然预计编译器会优化掉do { ... } while(false);循环,但还有另一种解决方案不需要该构造.解决方案是使用逗号运算符:
#define FOO(X) (f(X),g(X))
Run Code Online (Sandbox Code Playgroud)
甚至更加异乎寻常:
#define FOO(X) g((f(X),(X)))
Run Code Online (Sandbox Code Playgroud)
虽然这适用于单独的指令,但它不适用于构造变量并将其用作以下内容的一部分的情况#define:
#define FOO(X) (int s=5,f((X)+s),g((X)+s))
Run Code Online (Sandbox Code Playgroud)
有了这个,就会被迫使用do/while结构.
Cœu*_*œur 16
do {} while (0)并if (1) {} else确保将宏扩展为仅 1 条指令。除此以外:
if (something)
FOO(X);
Run Code Online (Sandbox Code Playgroud)
将扩展为:
if (something)
f(X); g(X);
Run Code Online (Sandbox Code Playgroud)
并且g(X)会在if控制语句之外执行。使用do {} while (0)and时可以避免这种情况if (1) {} else。
使用 GNU语句表达式(不是标准 C 的一部分),您有比do {} while (0)和更好的方法if (1) {} else来解决这个问题,只需使用({}):
#define FOO(X) ({f(X); g(X);})
Run Code Online (Sandbox Code Playgroud)
并且此语法与返回值兼容(注意do {} while (0)不是),如下所示:
return FOO("X");
Run Code Online (Sandbox Code Playgroud)
小智 10
Jens Gustedt的P99预处理器库(是的,这样的事情存在的事实也引起了我的注意!)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
Run Code Online (Sandbox Code Playgroud)
这个的基本原理是,与do { ... } while(0)构造不同,break并且continue仍然在给定块内部工作,但是((void)0)如果在宏调用之后省略分号则会产生语法错误,否则将跳过下一个块.(这里实际上没有"悬空的"问题,因为它else绑定到最近的if,即宏中的那个.)
如果您对使用C预处理器可以或多或少安全地完成的各种事情感兴趣,请查看该库.
小智 7
由于某些原因,我不能评论第一个答案......
你们中的一些人展示了带有局部变量的宏,但没有人提到你不能只在宏中使用任何名字!有一天它会咬住用户!为什么?因为输入参数被替换为宏模板.在您的宏示例中,您使用了可能最常用的变量名称i.
例如当下面的宏
#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)
Run Code Online (Sandbox Code Playgroud)
用于以下功能
void some_func(void) {
int i;
for (i = 0; i < 10; ++i)
FOO(i);
}
Run Code Online (Sandbox Code Playgroud)
宏不会使用在some_func开头声明的预期变量i,而是使用在宏的do ... while循环中声明的局部变量.
因此,永远不要在宏中使用常见的变量名!
| 归档时间: |
|
| 查看次数: |
87784 次 |
| 最近记录: |