【问题标题】:Undefined behavior inside void expressionsvoid 表达式中的未定义行为
【发布时间】:2019-12-02 14:11:37
【问题描述】:

C 实现是否需要忽略在评估 void 表达式期间发生的未定义行为,就好像评估本身从未发生过一样?

考虑到 C11,6.3.2.2 §1:

如果任何其他类型的表达式被评估为 void 表达式,则其值或指示符将被丢弃。 (计算 void 表达式的副作用。)

这与用于防止编译器警告未使用变量的常用习语有关:

void f() {
  int a;
  (void)a;
}

但是如果我们有未定义的行为,例如:

void f() {
  int a;
  (void)(1/0);
}

我可以安全地声称该程序不包含未定义的行为吗?标准说“它的值或指示符被丢弃”,但“表达式 (...) 被评估 (...)”,所以评估似乎发生了。

GCC/Clang 确实报告了未定义的行为,因为在这种情况下很明显,但在一个更微妙的例子中它们不会:

int main() {
  int a = 1;
  int b = 0;
  (void)(a/b);
  return 0;
}

即使使用 -O0,GCC 和 Clang 都不会评估 1/0。但即使演员表不作废也会发生这种情况,因此不具有代表性。

将论点推向极端,在我的第一个示例(a 未初始化)中对(void)a 的简单评估不会系统地触发未定义的行为吗?

ISO C11 6.3.2.1 §2 确实提到:

如果左值指定了一个可以使用寄存器存储类声明的具有自动存储持续时间的对象(从未使用过它的地址),并且该对象未初始化(未使用初始化程序声明并且没有对其执行分配在使用之前),行为是未定义的。

但是,在附件 J.2 未定义行为中,措辞略有不同:

在以下情况下行为未定义:

(...)

在需要指定对象的值的上下文中使用可以使用寄存器存储类声明的具有自动存储持续时间的对象的左值,但该对象未初始化。 (6.3.2.1)。

这个附件确实导致了这样的解释,即在其评估期间包含未定义行为的 void 表达式实际上并未被评估,但由于它只是一个附件,我不确定它的论证权重。

【问题讨论】:

  • 嗯,即使(void)a 也处于灰色区域。 6.3.2.2:“如果任何其他类型的表达式被评估为 void 表达式,则其值或指示符将丢弃。(评估 void 表达式的副作用。)”
  • 抽象机将在丢弃结果之前评估表达式。
  • 看看这个:https://www.godbolt.org/z/4tHKGg:实际上表达式被部分计算,即f函数被调用了3次,但是表达式中的除法和加法没有被计算(因为结果被忽略)。但是 IMO 编译器可以生成除法和加法的代码,然后忽略结果。
  • 对于第一句中问题的答案,“是否可以忽略在评估 void 表达式期间发生的未定义行为,就好像评估本身从未发生过一样?”,无疑是肯定的。 C 标准对未定义的行为没有任何要求,因此忽略未定义的行为是 C 实现的符合要求。不过,我认为这不是 Stack Overflow 问题的意图——我认为 anol 试图询问实现的用户是否可能依赖于没有效果的未定义行为;是 C 实现 需要 忽略它。...
  • @Virgule 一个实际的实现可以根据抽象机器评估表达式中不需要的部分,所以如果它是实际机器的未定义行为,它就是未定义行为句号。

标签: c language-lawyer void expression-evaluation lvalue-to-rvalue


【解决方案1】:

这与用于防止编译器 关于未使用变量的警告:

void f() {
  int a;
  (void)a;
}

是和不是。我认为这个习惯用法将一个未使用的变量变成了一个使用过的变量——它出现在一个表达式中——转换为void 用于防止编译器抱怨该表达式未使用的结果。但在技术、语言律师的意义上,该习语的特定表达会产生 UB,因为当 a 的值不确定时,子表达式 a 会进行左值转换。您已经引用了标准的相关文本。

但是如果我们有未定义的行为,例如:

void f() {
  int a;
  (void)(1/0);
}

我可以安全地声明这个程序不包含未定义的行为吗?

没有。

标准说“它的值或指示符被丢弃”,但是 “表达式(...)被评估(...)”,所以评估似乎 发生。

是的,正如前面示例中的表达式a 也被评估,也产生了UB。 UB 源于对内部子表达式的评估。转换为 void 类型是一个单独的考虑因素,就像转换为任何其他类型一样。

GCC/Clang 确实报告了未定义的行为,因为它在 这种情况,但在一个更微妙的例子中,他们没有:

此处不能将编译器行为视为指示性行为。 C 不需要编译器来诊断大多数未定义的行为,即使是那些原则上可以在编译时检测到的行为。事实上,重要的是要认识到由错误代码引起的 UB 首先发生在在编译时,当然,如果生成了可执行文件,那么它也会表现出 UB。

将论点推向极端,不是简单的评估 (void)a 在我的第一个示例中(a 未初始化)系统地 触发未定义的行为?

是的,正如我已经说过的。但这并不意味着包含此类结构的程序有义务mis表现。作为实现质量问题,我认为希望表达式语句(void)a; 将被编译器接受并且根本没有相应的运行时行为是合理的。但我不能依靠语言标准来支持我。

这个附件确实导致了void 表达式的解释 在其评估期间包含未定义的行为实际上并不是 评估,但由于它只是一个附件,我不确定它 争论的重量。

标准规范文本的简单措辞在这里就足够了。附件不是规范性的,但如果对如何解释规范性文本有任何疑问,那么标准的信息性部分,如附件 J,是整理时考虑的来源之一(但它们仍然只是提供信息)。

【讨论】:

    猜你喜欢
    • 2012-02-21
    • 1970-01-01
    • 2013-01-13
    • 1970-01-01
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 2021-04-23
    • 2011-09-14
    相关资源
    最近更新 更多