【发布时间】: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