这个无括号的 C 预处理器定义安全吗?

Is this parenthesis-free C preprocessor definition safe?

在我的 /usr/include 目录中,至少有两个 #define NULL 0 为 C++ 代码定制的变体1:

#define NULL 0    // from rpc/types.h
#define NULL (0)  // from libio.h

我觉得肯定有反例,第一个不安全,但是我没法制作。

否则,关于为什么在这种情况下不包含括号是安全的(例如,非正式的 "proof of correctness"),是否存在一些令人信服的论点?


1 即不包括变量 #define NULL ((void*)0),它对 C 有用,但在 C++ 中无效。

我相信后者可能是由于 cargo-culting 而出现的,即 rule/reflex 始终将预处理器定义放在括号中以备不时之需。

没有区别。唯一具有更高优先级的运算符是 ::++--,它们不适用于 0(0)

我看到的唯一有趣的区别是混淆:

#define NULL (0)

void f(int x)
{
  // Do something with x
}

int main()
{
  f NULL; // This code compiles
  return 0;
}

如果我们谈论安全,有人可能会争辩说 #define NULL 0 实际上比 #define NULL (0) 更安全。

你看,后者使你能够做这样的事情:a = func NULL; 而不是 a = func(0);,它可以在生产 C 代码中看到的任何人的大脑中产生不受控制的核反应。这很不安全,你知道 =)

它是安全的,至少就不会出现意外的运算符优先级后果而言。

任何没有运算符的纯文字宏定义总是安全的。例如...

#define FOO 1
#define NAME "Fred"
#define malloc my_malloc

因为还没有人明确地拼写出来...

is there some compelling argument about why it would be safe to not include the parentheses in this case (e.g., an informal "proof of correctness")?

括号是分组结构。他们采用 expression 并将其转换为 primary-expression (使用标准语法中的名称)。一个完整的 primary-expression 不能是 "reached into" 并被应用于它的运算符拆分,无论该运算符的优先级如何。除了函数调用和 if 的评论中提到的问题外,这就是他们所做的一切;它们在宏中使用,以确保扩展结果具有与源代码中相同的优先级(常量看起来像一个原子;primary-expression 的处理方式相同作为表达式结构中的一个)。

数字0是句法原子。由于是文字常量,它已被定义为 primary-expression。运算符拆分的表达式没有内部结构,优先级问题甚至不适用。由于括号没有更改的优先级,将其包裹在括号中会将 primary-expression 转换为另一个 primary-expression,即按字面意思没什么。

参考:C11 6.5.1 "Primary expressions", C++11 5.1.1 "Primary expressions".