【问题标题】:C preprocessor: does this code lead to well-defined behavior? [closed]C 预处理器:这段代码会导致明确定义的行为吗? [关闭]
【发布时间】:2023-03-30 07:15:01
【问题描述】:

此代码是否会导致明确定义的行为?如果不是,那么违反了 C 标准的哪些规则/声明?它们究竟是如何被违反的?

代码 #1:

#define B(a1, ...)  a1
int main ( void )
{
    return B(0);
}

代码 #2:

#define M1(...)     M2(0
#define M2(a)       a
int main ( void )
{
    return M1(0));
}

【问题讨论】:

  • @UnslanderMonica,预处理器标准中的大多数“应”确实出现在约束部分,因此违反它们需要诊断。但并非所有都是这种情况,这里给出的第一个例子确实没有定义行为。
  • @JensGustedt 很好。这些是恕我直言,标准中的技术缺陷 - 没有必要对此未定义...... :(
  • @UnslanderMonica,我不这么认为。 UB 在 C 标准中有不同的原因。其中之一是让实现松懈以提供自己的定义,预处理器中的此类 UB 可能就是这种情况。
  • @JensGustedt 为实现提供“松弛”通常会使该功能无用,因为您不能依赖可移植代码中的任何特定行为。但是我学到了一些新东西——我没有意识到这种基本的预处理器行为是未定义的。它可能至少是实现定义的,这通常意味着实现者至少会费心记录他们放入的行为。由于预处理器中未定义的行为,大多数实现甚至都不会费心记录它,所以你不能依赖它在下一个版本中不会改变,别介意在另一个编译器上......
  • @UnslanderMonica:关于“没用,因为您不能依赖可移植代码中的任何特定行为”:所以?并非每个人的目标都是创建通用可移植的代码。 C 的一个基本特征是它被设计为灵活的,因此它可以在许多平台上提供编程语言,并且在这方面取得了巨大的成功。不再需要每个人都必须在新处理器上学习汇编,或者制造商必须繁琐地构建一些新的编译器;有一个可以移植的通用编程基础……

标签: c c-preprocessor undefined-behavior c11


【解决方案1】:

此答案针对代码 #2。

代码的行为由 C 标准定义,结果为 0。在M1(0)) 中,M1(0) 是一个普通的宏调用。它被M2(0 取代。根据 C 2018 6.10.3.4 1,“然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。”所以M2(0 是生成的序列,) 开始后续的标记,因此扫描M2(0) 以查找更多要替换的宏名称。找到M2(0) 并替换为0

【讨论】:

    【解决方案2】:

    对于代码 #1,有此条款 (6.10.3 p4)

    否则,调用中的参数将多于实际参数 是宏定义中的参数(不包括 ...)。那里 应存在一个 ) 终止调用的预处理令牌。

    这里的“shall”表示调用的行为确实是未定义的。此处未定义的行为只是意味着该标准让实现在这种情况下可以做任何他们想做的事情。例如 gcc 和 clang 对此非常满意。

    【讨论】:

      猜你喜欢
      • 2011-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多