Yu *_*Hao 79 c language-lawyer
调用未定义行为的代码(在此示例中,除以零)将永远不会执行,程序是否仍未定义行为?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我认为它仍然是未定义的行为,但我在标准中找不到支持或否认我的任何证据.
那么,有什么想法吗?
Kei*_*son 70
让我们看一下C标准如何定义术语"行为"和"未定义的行为".
参考ISO 15 2011标准的N1570草案; 我不知道三个公布的ISO C标准(1990年,1999年和2011年)中的任何一个都存在任何相关差异.
第3.4节:
行为
外观或行为
好吧,这有点模糊,但我认为给定的陈述没有"外观",当然也没有"行动",除非它实际执行.
第3.4.3节:
未
使用的行为行为,在使用不可移植或错误的程序结构或错误数据时,本国际标准不对其施加任何要求
它说" 使用 "这种结构.标准没有定义"使用"这个词,所以我们回到了常用的英语含义.如果构造从未执行过,则不会"使用"构造.
根据该定义有一条说明:
注意可能的未定义行为包括完全忽略具有不可预测结果的情况,在转换或程序执行期间以环境特征(有或没有发出诊断消息)的特定文档执行,终止转换或执行(使用发布诊断消息).
因此,如果编译器的行为未定义,则允许编译器在编译时拒绝您的程序.但我对此的解释是,只有当它可以证明程序的每次执行都会遇到未定义的行为时,它才能这样做.我想,这意味着:
if (rand() % 2 == 0) {
i = i / 0;
}
Run Code Online (Sandbox Code Playgroud)
当然可以有未定义的行为,不能在编译时拒绝.
实际上,程序必须能够执行运行时测试以防止调用未定义的行为,并且标准必须允许它们这样做.
你的例子是:
if (0) {
i = 1/0;
}
Run Code Online (Sandbox Code Playgroud)
从来没有用0来执行除法.一个非常常见的习语是:
int x, y;
/* set values for x and y */
if (y != 0) {
x = x / y;
}
Run Code Online (Sandbox Code Playgroud)
如果分裂肯定有未定义的行为y == 0
,但它永远不会被执行y == 0
.行为已明确定义,并且出于同样的原因,您的示例已明确定义:因为潜在的未定义行为实际上永远不会发生.
(除非INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(是的,整数除法可以溢出),但这是一个单独的问题.)
在注释中(自删除后),有人指出编译器可能在编译时计算常量表达式.这是真的,但在这种情况下不相关,因为在上下文中
i = 1/0;
Run Code Online (Sandbox Code Playgroud)
1/0
不是一个恒定的表达.
甲常数表达式是一个句法类别减少到条件表达式(不包括分配和逗号表达式).生成常量表达式仅在实际需要常量表达式的上下文中出现在语法中,例如案例标签.所以,如果你写:
switch (...) {
case 1/0:
...
}
Run Code Online (Sandbox Code Playgroud)
然后1/0
是一个常量表达式 - 并且违反了6.6p4中的约束:"每个常量表达式应该计算为其类型的可表示值范围内的常量."因此需要进行诊断.但是赋值的右侧不需要常量表达式,只需要条件表达式,因此常量表达式的约束不适用.编译器可以评估它能够在编译时任何表情,但只有当行为是一样的,如果它在执行过程中评估(或在的情况下if (0)
,不()执行过程中进行评估.
(看起来完全像常量表达式的东西不一定是常量表达式,就像x + y * z
在序列x + y
不是加法表达式一样,因为它出现的上下文.)
这意味着我要引用的N1570第6.6节中的脚注:
因此,在以下初始化中,
static int i = 2 || 1 / 0;
表达式是值为1的有效整数常量表达式.
实际上与这个问题无关.
最后,有一些事情被定义为导致未定义的行为,而不是在执行期间发生的事情.附录J,C标准的第2部分(再次参见N1570草案)列出了从标准的其余部分收集的导致未定义行为的事物.一些例子(我不认为这是一个详尽的清单)是:
- 非空源文件不以新行字符结尾,该字符不会立即以反斜杠字符开头,也不会以部分预处理标记或注释结尾
- 令牌连接产生与通用字符名称的语法匹配的字符序列
- 除了标识符,字符常量,字符串文字,标题名称,注释或永远不会转换为标记的预处理标记之外,源文件中将遇到不在基本源字符集中的字符
- 标识符,注释,字符串文字,字符常量或标题名称包含无效的多字节字符,或者不在初始移位状态下开始和结束
- 相同的标识符在同一翻译单元中具有内部和外部链接
这些特殊情况是编译器可以检测到的.我认为他们的行为是未定义的,因为委员会不希望或不能在所有实施中强加相同的行为,并且定义一系列允许的行为是不值得的.它们并不属于"永远不会执行的代码"类别,但我在这里提到它们是为了完整性.
Pas*_*uoq 31
该文章讨论了第2.6节这样一个问题:
int main(void){
guard();
5 / 0;
}
Run Code Online (Sandbox Code Playgroud)
作者认为该程序是在guard()
未终止时定义的.他们还发现自己区分"静态未定义"和"动态未定义"的概念,例如:
标准11背后的意图似乎是,一般情况下,如果不容易为它们生成代码,则静态地定义情况.只有在可以生成代码时,才能动态地定义情境.
11)与委员会成员的私人通信.
我建议看看整篇文章.总之,它描绘了一致的画面.
该文章的作者必须与委员会成员讨论该问题,这一事实证实该标准目前在您的问题答案上是模糊的.
在这种情况下,未定义的行为是执行代码的结果.因此,如果未执行代码,则没有未定义的行为.
如果未定义的行为仅仅是代码声明的结果(例如,如果某些变量阴影的情况未定义),则未执行的代码可以调用未定义的行为.