为什么a = i + i ++未定义且未指定行为

时间:2012-09-24 20:43:50

标签: c++

我读了几个关于未定义行为和序列点的非常好的答案(例如Undefined behavior and sequence points),我理解,

   int i = 1;
   a = i + i++; //this is undefined behaviour
根据C ++标准,

是未定义的代码。但是,未定义的行为背后的深层原因是什么?是否足以使其成为未指明的行为? 正常的论点是,通过几个序列点,C ++编译器可以更好地针对不同的体系结构进行优化,但是不会让它未指定允许这些优化吗?在

   a = foo(bar(1), bar(2)); //this is unspecified behaviour

编译器也可以优化,并且它不是未定义的行为。在第一个例子中,似乎很清楚,a是2或3,所以语义似乎对我来说很清楚。我希望有一个推理,为什么有些事情没有说明,有些事情是未定义的。

5 个答案:

答案 0 :(得分:9)

所有的优化。例如,Itanium可以并行执行添加和增量,并且可能会因为尝试执行此类操作而出现硬件异常。

但这是一个完全微观的优化,编写编译器来利用这个非常困难,并且它是一个非常罕见的架构,可以做到(当时没有,IIRC,它主要是假想)。所以现实情况是,截至2012年,没有理由不将其定义为明确的行为,事实上,C ++ 11更多地将这些情况定义得很明确。

答案 1 :(得分:3)

从C ++的角度来看,我认为答案非常简单:它被定义为未定义的行为,因为C很久以前就已经使它成为未定义的行为,并且基本上没有改变它的潜在收益。

这表明我猜的更多是预期的问题:为什么C会做出这种未定义的行为?

我认为答案并不那么简单。一种可能性是简单的谨慎 - 知道在编写C标准时,C已经在许多机器上实现,部署和使用。当时有相当数量的机器看起来像我仍然看到的很多代码:最初只是作为个人实验设计的东西,运行良好,最终被指定为“生产”,甚至没有通过最令人震惊的问题。因此,即使没有人知道硬件会破坏,也没有人能确定这些硬件也不存在,所以最安全的做法就是把它称为UB,然后用它完成。

另一种可能性是,它有点超出了简单的谨慎。即使我们对现代硬件感觉相当安全,但当时人们真正知道的硬件可能存在重大问题,并且(特别是如果与该硬件相关的供应商在委员会中有代表)允许C运行硬件被认为是重要的。

另一种可能性是,尽管没有人知道(或者甚至担心这种可能性)某些现有的实施可能会破坏,但他们预见到未来可能会破坏某些东西,所以未定义的行为被视为一种方式未来至少在某种程度上证明语言。

最后一种可能性是,无论是谁写出标准的那一部分,只要他们提出似乎可以接受的 一套规则,就会转移到其他事情上,即使它们可能已经出现与其他规则至少有些人可能更喜欢。

如果我不得不猜测,我会说这可能是我给出的第三种和第四种可能性的组合 - 委员会意识到并行计算的发展而不知道它最终会如何发挥作用,所以对于那些写这篇文章的人来说,最大限度地利用实现方面的宽容似乎是获得共识的最简单/最简单的途径,这样他们就可以完成它并转向更大更好的东西。

答案 2 :(得分:2)

未定义的行为与未指定的行为之间存在巨大差异。未指定的行为是格式良好的(即合法的),但标准使编译器供应商在实现方面有一定的自由度。未定义的行为是一种似乎在语法上正确的暴行。认为行为“未定义”而不是违规行为(编译器必须拒绝)的主要原因是,有时未定义的行为可能很难诊断。

答案 3 :(得分:0)

a = foo(bar(1), bar(2)); // this is unspecified behaviour

两个函数调用可以按任何顺序进行,但它们仍然是两个不同的函数调用。即使内联呼叫,也不允许机器指令重叠。实际上,它们确实重叠了很多,但优化器被限制为生成严格的代码 - 如果函数调用是分开的。

a = i + i++; // this is undefined behaviour

使用标量i,不需要分离:从i获取的cpu指令,添加和后增量,自由混合,并允许优化器假装它不知道我在左边我在右边是一样的。当违反这个先决条件时,无法确定它会产生什么样的破坏组件。因此,未定义。

答案 4 :(得分:0)

MIPS 1是带有Load Delay插槽的合理实现。执行加载不是即时的。结果仅在下一条指令开始后可见。对于编译器来说,这没什么大不了的。只需在下一个插槽中放入一条不相关的指令。

当然,编译器必须知道“无关”是什么。使用针对单个varibale的并发修改的C规则,编译器在查找必须不相关的指令时有更多的选择。如果在一个语句中出现两个操作,则它们必须对不同的变量进行操作,因此不相关。

相关问题