什么时候可以省略宏中参数周围的括号?

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,代码会按照我的回答执行

以上是关于什么时候可以省略宏中参数周围的括号?的主要内容,如果未能解决你的问题,请参考以下文章

在 MSVC 中处理宏中的额外括号

lua 函数调用的时候使用小括号和使用大括号有啥区别,如何定义?

方法

java图中代码改用Lambda表达式实现Comparator接口?

Confluence 6 在你用户宏中使用参数

Confluence 6 在你用户宏中使用参数