ano*_*nol 6 c expression-evaluation language-lawyer lvalue-to-rvalue
是否需要C实现来忽略无效表达式评估期间发生的未定义行为,就好像评估本身从未发生过一样?
考虑C11,6.3.2.2§1:
如果将任何其他类型的表达式评估为void表达式,则将其值或指示符丢弃。(将评估void表达式的副作用。)
这与用于防止编译器警告未使用的变量的常用用法有关:
void f() {
int a;
(void)a;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我们有未定义的行为,例如:
void f() {
int a;
(void)(1/0);
}
Run Code Online (Sandbox Code Playgroud)
我可以安全地声明该程序不包含未定义的行为吗?该标准说“它的值或指示符被丢弃”,但是“表达式(...)被评估(...)”,因此评估似乎确实发生了。
GCC / Clang确实报告了未定义的行为,因为在这种情况下很明显,但是在一个更微妙的示例中,它们没有:
int main() {
int a = 1;
int b = 0;
(void)(a/b);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
即使这样-O0,GCC和Clang都无法评估1/0。但这即使没有强制转换也会发生,因此它不具有代表性。
将论点推到极致,(void)a在我的第一个示例(a未初始化的地方)中的简单评估是否会系统地触发未定义的行为?
ISO C11 6.3.2.1§2确实提到:
如果左值指定了可以使用寄存器存储类声明的自动存储持续时间的对象(从未使用其地址),并且该对象未初始化(未使用初始化器声明,并且在使用前未对其进行任何赋值) ),则行为未定义。
但是,在附件J.2未定义行为中,措词略有不同:
在以下情况下,行为是不确定的:
(...)
在需要指定对象值的上下文中使用了一个左值,该值指定了可以用寄存器存储类声明的自动存储持续时间的对象,但是该对象未初始化。(6.3.2.1)。
本附件确实导致了这样的解释,即void在评估过程中包含未定义行为的表达式实际上并未得到评估,但是由于它只是附件,因此我不确定其论据的重要性。
这与用于防止编译器警告未使用的变量的常用用法有关:
Run Code Online (Sandbox Code Playgroud)void f() { int a; (void)a; }
是的,没有。我认为,惯用法将未使用的变量转换为已使用的变量-它出现在表达式中-强制转换void为防止编译器抱怨该表达式的结果未使用。但是从技术角度,从语言律师的角度来说,该惯用语的特定表达会产生UB,因为a当a的值不确定时,子表达式会进行左值转换。您已经引用了该标准的相关文本。
但是,如果我们有未定义的行为,例如:
Run Code Online (Sandbox Code Playgroud)void f() { int a; (void)(1/0); }我可以安全地声明该程序不包含未定义的行为吗?
没有。
该标准说“它的值或指示符被丢弃”,但是“表达式(...)被评估(...)”,因此评估似乎确实发生了。
是的,就像a您前面的示例中的表达式也被求值一样,它也会生成UB。UB来自内部子表达式的评估。转换为类型void是一个独立的考虑因素,就像转换为任何其他类型一样。
GCC / Clang确实报告了未定义的行为,因为在这种情况下很明显,但是在一个更微妙的示例中,它们没有:
此处不能将编译器行为视为指示。C不需要编译器来诊断大多数未定义的行为,甚至不需要在原则上在编译时就可以检测到的行为。确实,重要的是要认识到由不正确的代码引起的UB首先在编译时发生,但是当然可以肯定,如果生成了可执行文件,那么它也会显示UB。
将论点推到极致,
(void)a在我的第一个示例(a未初始化的地方)中的简单评估是否会 系统地触发未定义的行为?
是的,我已经说过了。但这并不意味着包含此类构造的程序必须行为不当。作为实现质量问题,我认为希望表达式语句(void)a;将被编译器接受并且完全没有相应的运行时行为是合理的。但是我不能依靠语言标准来支持我。
本附件确实导致了这样的解释,即
void在评估过程中包含未定义行为的表达式实际上并未得到评估,但是由于它只是附件,因此我不确定其论据的重要性。
在此,标准的规范文本的措辞很简单。附件不是规范性的,但是如果对规范性文本的解释方式有任何疑问,那么标准的内容丰富的部分(例如附件J)是在整理这些内容时要考虑的来源之一(但它们仍然仅提供信息)。