什么时候可以省略宏中参数周围的括号?
Posted
技术标签:
【中文标题】什么时候可以省略宏中参数周围的括号?【英文标题】:When can the parentheses around arguments in macros be omitted? 【发布时间】:2014-01-24 16:58:10 【问题描述】:我经常并且经常觉得宏定义中的一些围绕参数的括号是多余的。把所有东西都括起来太不方便了。如果我可以保证参数不需要括号,我可以省略括号吗?还是强烈推荐将它们都括起来?
我在写的时候想到了这个问题:
#define SWAP_REAL(a, b, temp) dodouble temp = a; a = b; b= temp;while(0)
我认为如果一个参数作为左值出现在宏中,括号可以省略,因为这意味着参数出现时没有其他操作。
我的理由是:
-
赋值符号的关联优先级仅高于逗号。
您不能让编译器误以为您的参数是一个带有逗号的表达式。例如,SWAP(a, b, b)
不会被成功解释为
dodouble temp = a, b; a, b = b; b= temp;while(0)
可以通过编译。
我说的对吗?谁能给我一个反例?
在下面的例子中,
#define ADD(a, b) (a += (b))
我认为参数a
不需要加括号。而且在这种特定情况下,也不需要参数b
,对吧?
@JaredPar:
#include <stdio.h> #define ADD(a, b) (a += (b)) int main() int a = 0, b = 1; ADD(a; b, 2); return 0;
这在我的 VS2010 上无法编译成功。 Error C2143: syntax error : missing ')' before ';'
简而言之,您不需要将在宏中作为左值出现的参数括起来,但强烈建议您将它们全部括起来。
宏规则:
-
不要制作有副作用的宏!
至于没有副作用的宏,把所有的参数都加括号就行了!
至于有副作用的宏,别再执着了!把它们都括起来! TnT
【问题讨论】:
如果您需要使用未完全括起来的宏,您将花费多少时间来确定它是否在所有情况下都安全? @KeithThompson 相反,如果您不知道需要用括号括起来的内容,您怎么知道自己正确编写了宏? 或许可以考虑相反的问题——人们可能会写出的宏实际上会被防御性括号破坏? 我只是讨厌用括号括起来怀疑变量的必要性的感觉。 我不确定你认为你用 MIN 演示了什么......我说过如果你省略参数周围的括号,事情将无法正常工作,并且你的测试出现错误 C2106证实了这一点。如果您使用带括号的宏参数,则不会有问题。所以,AFAICT,你同意我所说的。 【参考方案1】:在一些宏中,您可以将元素连接成一个字符串(可能使用#
运算符),或者使用##
运算符从宏参数中构建标识符。在这种情况下,您不要将参数括起来。
另外,我认为当宏参数本身作为函数参数传递时,你不必绝对用括号括起来:
#define CALLOC(s, n) calloc(s, n)
你可以调用这样一个宏 (CALLOC(3, 4)
) 来玩恶魔游戏,但你得到了你应得的(编译错误)——我不知道调用该宏的方法可以代替宏您编写的参数与直接调用 calloc()
的参数相同。
但是,如果您在大多数算术表达式中使用参数,则需要将它们括在括号中:
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
显然,如果您使用带有副作用的参数调用它,那么您将得到您所得到的。但是如果你写的话,这些论点不会被误解:
#define MIN(x, y) x < y ? x : y
然后调用它为:
MIN(x = 3 * y + 1, y = 2 * x - 2);
Moon 的 comment 建议使用 SWAP_INT
宏。
使用该宏的以下代码在使用默认选项编译时可以干净地编译,但在设置 -DWITH_PARENTHESES
时无法编译。
#include <stdio.h>
#ifdef WITH_PARENTHESES
#define SWAP_INT(a, b) do int temp = (a); (a) = (b); (b) = temp; while (0)
#else
#define SWAP_INT(a, b) do int temp = a; a = b; b = temp; while (0)
#endif
int main(void)
int p = 319;
int q = 9923;
int x = 23;
int y = 300;
printf("p = %8d, q = %8d, x = %8d, y = %8d\n", p, q, x, y);
SWAP_INT(p, q); // OK both ways
SWAP_INT(x, y); // OK both ways
printf("p = %8d, q = %8d, x = %8d, y = %8d\n", p, q, x, y);
SWAP_INT(x /= y, p *= q); // Compiles without parentheses; fails with them
printf("p = %8d, q = %8d, x = %8d, y = %8d\n", p, q, x, y);
return 0;
输出:
p = 319, q = 9923, x = 23, y = 300
p = 9923, q = 319, x = 300, y = 23
p = 41150681, q = 13, x = 0, y = 3165437
这不是一种安全或有效的整数交换方式——不带括号的宏不是一个好主意。
JFTR:在没有-DWITH_PARENTHESES
的情况下编译时,带有表达式的宏行是不可理解的:
do int temp = x /= y; x /= y = p *= q; p *= q = temp; while (0);
通常,swap 宏需要两个变量名——或数组元素,或结构或联合成员,或这些的任何组合。它不期望被赋予任意表达式。括号确保分配给x /= y
(不是左值)失败;如果没有括号,则表达式会被解释,但它与预期的不同(如果确实在“交换”两个这样的表达式时可以预期任何东西)。
【讨论】:
我在VS2010上试过CALLOC(3, 4)
,编译不通过。你能给我一些进一步的解释吗?我以为你的意思是“CALLOC(3, 4)
可以成功调用,但calloc(3, 4)
不能”。我是不是又误会你了?...
这是假的;它不应该编译;你会得到你应得的——一条错误消息——调用带有无效参数的宏/函数。【参考方案2】:
如果您在表达式中使用任何不是最低优先级的运算符,则省略是不安全的。我相信那是逗号。
请注意,我不声称在这些情况下它是安全的。有更多创造性的方法来打破宏。顺便说一句,首先不要使用具有副作用的宏。更一般地说,不要将宏用作函数。 (参见,例如,Effective C++)。
【讨论】:
谢谢,杰奇林。你给了我为什么会遇到这个问题的提示:我将带有副作用的代码封装为宏。如果宏中没有赋值,只需将所有变量括起来即可。但是在某些情况下,我想将两个或多个任务封装在一起,我认为它们不应该成为一个函数(它们太短了)。所以我将它们组合在一个宏中。在这种情况下,我是否仍应将它们组合为一个函数,尤其是在纯 C 中?【参考方案3】:这是一个明显不同的案例
ADD(x;y, 42)
使用括号会导致编译错误,但没有括号会导致代码编译。
(x;y) += 42; // With parens errors
x;y += 42; // Without parens compiles
这可能看起来像一个愚蠢的例子,但将宏组合在一起很容易导致像上面这样奇怪的代码表达式。
为什么要在这里冒险?就2个字符
【讨论】:
嗨,贾里德帕。感谢您的回答,但我认为您的示例是错误的。我尝试了您的代码,它会在编译期间转储错误。错误消息是“错误 C2143:语法错误:在 ';' 之前缺少 ')'”。此外,ADD 只是一个最简单的示例。在这种情况下“#define SWAP_INT(a, b) doint temp = a; a = b; b = temp;while(0)”,我认为 a 和 b 不必加括号。 @Moon 我已经验证如果定义了 x 和 y,代码会按照我的回答执行以上是关于什么时候可以省略宏中参数周围的括号?的主要内容,如果未能解决你的问题,请参考以下文章
lua 函数调用的时候使用小括号和使用大括号有啥区别,如何定义?