可以永远不会被执行的代码调用未定义的行为吗?
调用未定义行为的代码(在这个例子中,除以零)永远不会被执行,程序仍然是未定义的行为?
int main(void) { int i; if(0) { i = 1/0; } return 0; }
我认为这仍然是不确定的行为,但我无法在标准中find支持或否认我的任何证据。
那么,有什么想法?
我们来看看C标准是如何定义术语“行为”和“未定义的行为”的。
参考ISO C 2011标准的N1570草案; 我不知道在三个公布的ISO C标准(1990,1999和2011)中有任何相关的差异。
3.4节:
行为
外观或行动
好吧,这有点含糊,但我认为一个给定的语句没有“外观”,当然没有“行动”,除非它被实际执行。
3.4.3节:
未定义的行为
行为,在使用不可移植或错误的程序结构或错误的数据时,本国际标准对此没有要求
它说“ 使用 ”这样的结构。 “使用”这个词不是由标准定义的,所以我们回到普通的英语含义。 如果一个构造从来没有被执行过,那它就不是“被使用”
这个定义下有一个说明:
注意可能存在的未定义的行为范围从忽略完全不可预知的结果,在翻译或程序执行过程中以环境特征(有或没有发布诊断消息),终止翻译或执行发出诊断消息)。
所以如果编译器的行为是不确定的,编译器就可以在编译时拒绝你的程序。 但是我的解释是, 只有在能够certificate程序的每个执行都会遇到未定义的行为的情况下,它才能这样做。 这意味着,我认为这是:
if (rand() % 2 == 0) { i = i / 0; }
这当然可以有不确定的行为,不能在编译时被拒绝。
实际上,程序必须能够执行运行时testing来防止调用未定义的行为,并且标准必须允许它们这样做。
你的例子是:
if (0) { i = 1/0; }
从不执行除法0.一个非常普遍的习惯是:
int x, y; /* set values for x and y */ if (y != 0) { x = x / y; }
如果y == 0
,除法当然有未定义的行为,但是如果y == 0
则从不执行。 行为定义良好,出于同样的原因,您的示例已被很好地定义:因为潜在的未定义行为永远不会实际发生。
(除非INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(是的,整数除法可以溢出),但这是一个单独的问题。
在评论(自删除以来),有人指出编译器可能在编译时计算常量expression式。 这是真的,但在这种情况下不相关,因为在上下文中
i = 1/0;
1/0
不是一个常量expression式 。
常量expression式是一种语法类别,可以简化为条件expression式 (不包括赋值语句和逗号expression式)。 生产常量expression式 仅在实际需要常量expression式的情况下出现在语法中,例如case标签。 所以如果你写:
switch (...) { case 1/0: ... }
那么1/0
是一个常量expression式 – 违反了6.6p4中的约束条件:“每个常量expression式的值应该是一个常量,它的types是可表示值的范围”,所以需要诊断。 但是赋值的右侧不需要常量expression式 ,而只需要一个条件expression式 ,所以对常量expression式的约束不适用。 编译器可以在编译时评估它能够执行的任何expression式,但前提是行为与在执行过程中评估的行为相同(或者在if (0)
的上下文中,在执行()期间不评估。
(看起来像一个常量expression式的东西不一定是一个常量expression式 ,就像在x + y * z
,序列x + y
不是一个加法expression式,因为它出现在上下文中。
这就是我要引用的N1570第6.6节中的脚注:
因此,在下面的初始化中,
static int i = 2 || 1 / 0;
该expression式是一个有效的整数常量expression式,值为1。
实际上与这个问题没有关系。
最后,有一些东西被定义为导致未定义的行为,而不是执行期间发生的事情。 C标准的附录J第2节(再次参见N1570草案 )列出了从标准的其余部分收集到的导致未定义行为的事情。 一些例子(我不认为这是一个详尽的列表)是:
- 一个非空的源文件不会以一个换行符结束,换行符不是以反斜杠字符开始,而是以部分预处理符号或注释结束
- 令牌连接产生一个匹配通用字符名称的语法的字符序列
- 除了标识符,字符常量,string文本,标题名称,注释或从未转换为令牌的预处理标记之外,在源文件中遇到不在基本源字符集中的字符
- 标识符,注释,string文字,字符常量或标题名称包含无效的多字节字符,或者不以初始转换状态开始和结束
- 相同的标识符在同一翻译单元中具有内部和外部链接
这些特定的情况是编译器可以检测到的。 我认为他们的行为是不确定的,因为委员会不希望或不可能在所有的实施中强加相同的行为,而定义一系列允许的行为是不值得的。 它们并不属于“永远不会执行的代码”的范畴,但为了完整性,我在这里提到它们。
本文在2.6节讨论这个问题:
int main(void){ guard(); 5 / 0; }
作者认为,程序是在guard()
不终止的情况下定义的。 他们还发现自己区分“静态未定义”和“dynamic未定义”的概念,例如:
标准11背后的意图似乎是,一般情况下,如果不容易为它们生成代码,那么情况就是静态的。 只有当代码可以被生成时,情况才能被dynamic地定义。
11)与委员的私人通信。
我会build议看看整个文章。 综合起来,它绘制了一个一致的图片。
文章的作者不得不与委员讨论这个问题,这一事实certificate了这个标准目前对你的问题的答案是模糊的。
在这种情况下,未定义的行为是执行代码的结果。 所以如果代码没有执行,就没有未定义的行为。
如果未定义的行为仅仅是代码声明的结果(例如,如果某些情况下的variablesshadowing是未定义的),那么未执行的代码可能会调用未定义的行为。
我会去与这个答案的最后一段: https : //stackoverflow.com/a/18384176/694576
UB是一个运行时问题,而不是编译时问题…
所以,不,没有UB被调用。
只有当标准突破变化,你的代码突然不再是“永远不会被执行”。 但是我不认为这会导致“未定义的行为”。 它没有造成任何事情 。
关于未定义行为的问题,通常很难将正式方面与实际方面分开。 这是1989年标准中未定义行为的定义(我手边没有更新的版本,但我不认为这会有很大的改变):
1个未定义的行为 行为,在使用非移植或错误的程序构造或者 错误的数据,本国际标准对此没有要求 2注意可能存在未定义的行为范围,从完全忽略情况 不可预知的结果,在翻译或程序执行期间performance不佳 以有logging的方式performance环境特征(有或没有 发出诊断消息),终止翻译或 执行(发出诊断消息)。
从正式的angular度来看,我会说你的程序确实调用了未定义的行为,这意味着标准并不要求运行时会做什么,只是因为它包含零除。
另一方面,从实际的angular度来看,我会惊讶地发现一个不像你直觉上期待的那样的编译器。
这个标准说,正如我记得的那样,从一开始就允许做任何事情,一个规则被打破了。 也许有一些具有全球特色的特殊事件(但我从来没有听说过或读过类似的东西)…所以我会说:不,这不能是UB,因为只要行为明确定义0就是全部假,所以规则不能在运行时被破坏。
我认为这仍然是不确定的行为,但我无法在标准中find支持或否认我的任何证据。
我认为该程序不会调用未定义的行为。
缺陷报告#109解决了类似的问题,并说:
而且,如果给定程序的每个可能的执行都会导致不确定的行为,则给定的程序不是严格符合的。 一致的实现不能仅仅因为某些可能的程序执行会导致未定义的行为而不能转化严格符合的程序。 因为foo可能永远不会被调用,所以给出的例子必须通过符合的实现来成功地转换。
这取决于如何定义expression式“未定义行为”,以及语句的“未定义行为”是否与程序的“未定义行为”相同。
这个程序看起来像C,因此对编译器使用的C标准(如某些答案所做的)进行更深入的分析是恰当的。
如果没有特定的标准,正确的答案是“取决于”。 在编译器的猜测中,在某些语言中,第一个错误之后的编译器试图猜测程序员可能意味着什么并且仍然生成一些代码。 在其他更纯粹的语言中,一旦未定义,未定义就会传播到整个程序。
其他语言有一个“有界错误”的概念。 对于一些有限的错误,这些语言定义了错误可能产生的程度。 特别是隐含垃圾回收的语言,经常会影响错误是否使input系统失效,或者不会。