c - 空表达式中未定义的行为



是否需要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。但即使没有演员阵容,这种情况也会发生,所以它不具有代表性。

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

ISO C11 6.3.2.1 §2 确实提到:

如果 lvalue 指定了一个自动存储持续时间的对象,该对象本可以使用寄存器存储类声明(从未获取其地址),并且该对象未初始化(未使用初始值设定项声明,并且在使用之前未对其执行任何赋值),则行为未定义。

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

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

(...

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

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

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

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未初始化) 触发未定义的行为?

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

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

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

最新更新