【问题标题】:Is this parenthesis-free C preprocessor definition safe?这个无括号的 C 预处理器定义安全吗?
【发布时间】:2015-08-01 11:14:11
【问题描述】:

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

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

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

否则,是否有一些令人信服的论据说明为什么在这种情况下不包括括号是安全的(例如,非正式的“正确性证明”)?


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

【问题讨论】:

  • 在 C++ 中,无论如何你都应该使用nullptr
  • #define NULL (void*)0 不是 C++ 中NULL 的有效定义。
  • "...第一个不安全的地方" - 我更担心(0) 替代...允许一些奇怪的代码,如myfunc NULL致电myfunc(0)
  • @PeterSchneider:我注意到 Mints97 对 unwind 答案的评论添加了 if NULL... 显然 [do] while NULL 也是一个问题。相当狂野!我可以想象一个新手尝试int* p = new NULL int; 并在地址0 获得unexpected placement new,但他们很可能会自己添加括号;-)。
  • @TonyD 我不认为反对是有效的,因为我们总是使用#define FOUR (2+2) 之类的东西来避免3*FOOR 的问题,并且总是允许使用myfunc FOUR 之类的奇怪代码。

标签: c++ c-preprocessor


【解决方案1】:

我相信后者可能是由于 cargo-culting 而出现的,即始终将预处理器定义放在括号中的规则/反射。

【讨论】:

  • 他们可以有不同的解释。一个可以使用if NULL 而另一个不能 =)
  • @Mints97 True .. 它“看起来”不像有效代码,但由于宏中的括号而变得如此,这当然是可怕的,但仍然是一个实际的区别。答案已编辑。
  • @PeteBecker:即使圆括号碰巧不是必需的,但它们的省略可能会迫使那些将来检查代码的人花费更多的精力来确定它们真正是不必要的而不是简单地查看多余的括号。
  • @supercat - (((a*b)+(c*d))-(e*f)) 迫使我理清那些糟糕的地狱般愚蠢的括号是什么意思。如果我想这样做,我会在 LISP 中编程。虽然冗余括号确实有时会有所帮助,但在我看到的许多堆栈溢出代码示例中,它们只是杂乱无章。
  • @PeteBecker:在那个特定的例子中,我通常同意你的看法,除非目的是特别描述操作顺序。不过,围绕宏及其参数,我认为冗余通常是一件好事。尽管将其扩展到文字可能会有点远,但在宏混合有正常量和负常量的情况下,特别是如果某些正值可能需要更改为负值,即使是正文字也可能会导致额外的括号更统一的外观,并确保负三写为(-3) 而不是-3
【解决方案2】:

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

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

#define NULL (0)

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这是安全的,至少在没有意外的运算符优先级后果方面。

      任何只是没有运算符的普通文字的宏定义总是安全的。比如……

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

      【讨论】:

      • 当文字用括号括起来时,我认为字符串文字连接不起作用。如果出于某种原因希望防止将字符串文字连接成更大的字符串常量,我认为在它周围添加括号就可以了。
      【解决方案5】:

      由于还没有人明确说明...

      关于为什么在这种情况下不包括括号是安全的,是否有一些令人信服的论据(例如,非正式的“正确性证明”)?

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

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

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

      【讨论】:

      • “将其包装在其中(括号)会将主表达式转换为另一个主表达式”当 ( 之前没有像函数调用那样被编译器使用时为真。在函数调用情况下,(0) 成为有效的带括号的 ( identifier-list ) 而不是简单的主表达式。因此#define NULL (0) sqrt NULL ; 编译。
      • @chux 当然,但这是a)从相反角度回答(“为什么这没有帮助”与“为什么这会伤害”); b) 以上已充分涵盖。这只是 OP 要求的“证明”。
      猜你喜欢
      • 2014-04-05
      • 2015-03-29
      • 2016-09-04
      • 2012-02-21
      • 1970-01-01
      • 2013-03-29
      • 2018-02-10
      • 2019-05-13
      • 2021-01-17
      相关资源
      最近更新 更多