void 表达式中的未定义行为

Undefined behavior inside void expressions

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

考虑 C11,6.3.2.2 §1:

If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

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

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

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

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

我可以肯定地说这个程序不包含未定义的行为吗?标准说 "its value or designator is discarded",但 "expression (...) is evaluated (...)",所以评估似乎确实发生了。

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

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

即使使用 -O0,GCC 和 Clang 都不会评估 1/0。但是即使没有转换为 void 也会发生这种情况,所以它不具有代表性。

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

ISO C11 6.3.2.1 §2 确实提到:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

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

The behavior is undefined in the following circumstances:

(...)

An lvalue designating an object of automatic storage duration that could have been declared with the register storage class is used in a context that requires the value of the designated object, but the object is uninitialized. (6.3.2.1).

这个附件确实导致了这样的解释,即 void 表达式在其求值过程中包含未定义的行为实际上并没有被求值,但由于它只是一个附件,我不确定它的争论分量。

This is related to the common idiom used to prevent compilers from warning about unused variables:

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

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

But what if we have undefined behavior, such as:

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

Can I safely claim that this program contains no undefined behavior?

没有

The standard says that "its value or designator is discarded", but the "expression (...) is evaluated (...)", so the evaluation does seem to take place.

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

GCC/Clang do report the undefined behavior, since it is obvious in this case, but in a more subtle example they don't:

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

Pushing the argument to its extreme, wouldn't the simple evaluation of (void)a in my first example (where a is uninitialized) systematically trigger undefined behavior?

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

This annex does lead to the interpretation that a void expression containing undefined behavior during its evaluation is not actually evaluated, but since it's just an annex, I'm not sure of its argumentative weight.

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