4 回答
TA贡献1804条经验 获得超8个赞
该do ... while
和if ... else
在那里让这个后您的宏分号总是意味着同样的事情。假设你有类似第二个宏的东西。
#define BAR(X) f(x); g(x)
现在,如果你要BAR(X);
在一个if ... else
语句中使用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)
上面的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预处理器时,这是他们担心的事情。
TA贡献1811条经验 获得超5个赞
宏是复制/粘贴的文本,预处理器将放入正版代码中; 宏的作者希望替换产生有效的代码。
有三个好的“提示”可以成功:
帮助宏的行为像真正的代码
正常代码通常以分号结束。如果用户查看不需要的代码......
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) ;
g
无论值如何,都将执行该功能bIsOk
。
这意味着我们必须向宏添加一个范围:
#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.}
TA贡献1772条经验 获得超5个赞
您可能还想补充一点,宏语法也可以使用简单的“if”语句防止可能更危险(因为没有错误)的意外行为:
#define FOO(x) f(x); g(x)if (test) FOO( baz);
扩展为:
if (test) f(baz); g(baz);
这在语法上是正确的,所以没有编译器错误,但可能有意外的结果,g()将始终被调用。
TA贡献1757条经验 获得超7个赞
上述答案解释了这些结构的含义,但两者之间存在显着差异,未提及。其实,还有一个原因,更喜欢do ... while
到if ... else
结构。
if ... else
构造的问题是它不会强迫你输出分号。喜欢这段代码:
FOO(1)printf("abc");
虽然我们遗漏了分号(错误地),但代码将扩展为
if (1) { f(X); g(X); } elseprintf("abc");
并将静默编译(虽然一些编译器可能会发出无法访问代码的警告)。但该printf
声明永远不会被执行。
do ... while
构造没有这样的问题,因为之后唯一有效的标记while(0)
是分号。
- 4 回答
- 0 关注
- 830 浏览
添加回答
举报