永远不会执行的代码可以调用未定义的行为吗?

时间:2013-08-22 15:38:37

标签: c

调用未定义行为的代码(在此示例中,除以零)将永远不会执行,程序是否仍未定义行为?

int main(void)
{
    int i;
    if(0)
    {
        i = 1/0;
    }
    return 0;
}

我认为它仍然是未定义的行为,但我在标准中找不到支持或否认我的任何证据。

那么,有什么想法吗?

8 个答案:

答案 0 :(得分:71)

让我们看一下C标准如何定义术语"行为"和"未定义的行为"。

参考ISO C 2011标准的N1570草案;我不知道三个公布的ISO C标准(1990年,1999年和2011年)中的任何一个都存在任何相关差异。

第3.4节:

  

<强>行为
  外表或行动

好吧,这有点模糊,但我认为某个陈述没有&#34;外观&#34;,当然没有&#34;行动&#34;,除非它&# 39; s实际执行了。

第3.4.3节:

  

未定义的行为
  在使用不可移植或错误的程序结构或错误数据时,   本国际标准没有要求

它表示&#34; 在使用时&#34;这种结构。单词&#34;使用&#34;标准没有定义,所以我们回到常用的英文含义。构造不是&#34;使用&#34;如果它从未执行过。

根据该定义,有一条注释:

  

注意可能的未定义行为包括忽略这种情况   完全具有不可预测的结果,在翻译过程中表现出色   或者以文件化的方式执行程序   环境(有或没有发出诊断信息),到   终止翻译或执行(发布a   诊断信息)。

因此,如果编译器的行为未定义,则允许编译器在编译时拒绝您的程序 。但我对此的解释是,如果能够证明程序的每次执行都会遇到未定义的行为,那么它只能 。我认为这意味着:

if (rand() % 2 == 0) {
    i = i / 0;
}

当然可以具有未定义的行为,在编译时不能被拒绝。

实际上,程序必须能够执行运行时测试以防止调用未定义的行为,并且标准必须允许它们这样做。

你的例子是:

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,它永远不会被执行。行为已明确定义,并且出于同样的原因,您的示例已明确定义:因为 potential 未定义的行为实际上永远不会发生。

(除非INT_MIN < -INT_MAX && x == INT_MIN && y == -1(是的,整数除法可以溢出),但这是一个单独的问题。)

在注释中(自删除​​后),有人指出编译器可能在编译时计算常量表达式。这是真的,但在这种情况下不相关,因为在

的背景下
i = 1/0;

1/0 不是常量表达式

常量表达式是一种语法类别,它缩减为条件表达式(不包括赋值和逗号表达式)。生产常量表达式出现在实际需要常量表达式的上下文中的语法 中,例如案例标签。所以,如果你写:

switch (...) {
    case 1/0:
    ...
}

然后1/0是一个常量表达式 - 并且违反6.6p4中的约束:&#34;每个常量表达式应计算为可表示范围内的常量 其类型的值。&#34;,因此需要诊断。但是赋值的右侧不需要常量表达式,只需要条件表达式,因此对常量表达式的约束不适用。编译器可以在编译时评估它能够执行的任何表达式,但前提是行为与在执行期间评估的行为相同(或者,在if (0)的上下文中,在执行期间评估()。

(看起来像常量表达式的东西不一定是常量表达式,就像在x + y * z中一样,序列{{1 }}不是 additive-expression ,因为它出现的上下文。)

这意味着N1570第6.6节中的脚注我将引用:

  

因此,在以下初始化中,
      x + y
  表达式是一个有效的整数常量表达式,其值为1。

实际上与此问题无关。

最后,有一些事情被定义为导致未定义的行为,这些行为与执行期间发生的事情无关。附录J,C标准的第2部分(再次参见N1570 draft)列出了导致未定义行为的事物,从标准的其余部分收集。一些例子(我不是声称这是一个详尽的清单)是:

  
      
  • 非空源文件不以新行字符结尾,该字符不会立即以反斜杠字符开头或以部分结尾   预处理令牌或评论
  •   
  • 令牌连接生成与通用字符名称
  • 的语法匹配的字符序列   
  • 源文件中遇到基本源字符集中没有的字符,标识符,字符常量,字符串除外   文字,标题名称,注释或预处理标记   从未转换为令牌
  •   
  • 标识符,注释,字符串文字,字符常量或标题名称包含无效的多字节字符或不开始   并以最初的班次状态结束
  •   
  • 同一标识符在同一翻译单元中具有内部和外部链接
  •   

这些特殊情况是编译器可以检测到的事情。我认为他们的行为是不明确的,因为委员会并不想或不可能在所有实施中强加同样的行为,并且定义一系列允许的行为并不值得付出努力。它们并不属于永远不会被执行的代码类别,但我在这里提到它们是为了完整。

答案 1 :(得分:31)

article在第2.6节

中讨论了这个问题
int main(void){
      guard();
      5 / 0;
}

作者认为该程序是在guard()未终止时定义的。他们还发现自己区分了“静态未定义”和“动态未定义”的概念,例如:

  

标准 11 背后的意图似乎是,一般情况下,如果不容易为它们生成代码,则静态地定义情况。只有在可以生成代码时,才能动态地定义情况。

     

11)与委员会成员的私人通信。

我建议查看整篇文章。总之,它描绘了一致的画面。

该文章的作者必须与委员会成员讨论该问题,这一事实证实该标准目前对您的问题的答案是模糊的。

答案 2 :(得分:5)

在这种情况下,未定义的行为是执行代码的结果。因此,如果代码未执行,则没有未定义的行为。

如果未定义的行为仅仅是代码声明的结果(例如,如果未定义某些变量阴影的情况),则未执行的代码可以调用未定义的行为。

答案 3 :(得分:2)

我会回答这个答案的最后一段:https://stackoverflow.com/a/18384176/694576

  

... UB是运行时问题,而不是编译时问题......

所以,不,没有调用UB。

答案 4 :(得分:2)

标准说,正如我记得的那样,从一开始就允许做任何事情,一条规则被打破了。也许有一些具有全球风味的特殊事件(但我从未听过或读过类似的东西)...所以我会说:不,这不能是UB,因为只要行为定义得很好0就是总是false,因此规则不能在运行时被破坏。

答案 5 :(得分:2)

只有当标准进行重大更改并且您的代码突然不再“永远不会被执行”时。但我没有看到任何逻辑方式导致“未定义的行为”。它不会导致任何

答案 6 :(得分:2)

关于未定义行为的主题,通常很难将形式方面与实际方面区分开来。这是1989年标准中未定义行为的定义(我手头没有更新的版本,但我不认为这有很大的改变):

1 undefined behavior
  behavior, upon use of a nonportable or erroneous program construct or of
  erroneous data, for which this International Standard imposes no requirements
2 NOTE Possible undefined behavior ranges from ignoring the situation completely
  with unpredictable results, to behaving during translation or program execution
  in a documented manner characteristic of the environment (with or without the
  issuance of a diagnostic message), to terminating a translation or
  execution (with the issuance of a diagnostic message).

从正式的角度来看,我会说你的程序确实会调用未定义的行为,这意味着标准对运行时的行为没有任何要求,只是因为它包含除零。

另一方面,从实际的角度来看,我会惊讶地发现编译器的行为并不像你直觉所期望的那样。

答案 7 :(得分:2)

  

我认为它仍然是未定义的行为,但我在标准中找不到支持或否认我的任何证据。

我认为该程序不会调用未定义的行为。

Defect Report #109解决了类似的问题并说:

  

此外,如果每个可能执行给定程序将导致未定义的行为,则给定的程序不严格符合。   一致的实现必须简单地转换严格符合的程序,因为该程序的某些可能的执行将导致未定义的行为。因为foo可能永远不会被调用,所以给定的示例必须通过一致的实现成功翻译。