C++ 为什么a=i+;i++;未定义和未指定的行为

C++ 为什么a=i+;i++;未定义和未指定的行为,c++,C++,我通读了一些关于未定义行为和序列点(例如)的非常好的答案,我理解 int i = 1; a = i + i++; //this is undefined behaviour 是未定义的代码,根据C++标准。但是,它背后的深层原因是什么,是未定义的行为?这难道不足以让它成为一种不确定的行为吗? 通常的论点是,通过很少的序列点,C++编译器可以更好地为不同的体系结构优化,但不会让它不指定,也允许那些优化。在 a = foo(bar(1), bar(2)); //this is u

我通读了一些关于未定义行为和序列点(例如)的非常好的答案,我理解

   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似乎很清楚,因此语义对我来说似乎很清楚。我希望有一个原因,为什么有些东西是未指定的,而有些东西是未定义的。

不是所有这些优化。例如,安腾可以并行执行加法和增量运算,并且它可能会因为尝试这样做而出现硬件异常

但这是一个完全微观的优化,编写编译器来利用这一点是非常困难的,而且它是一个非常罕见的体系结构,可以做到这一点(当时还不存在,IIRC,它主要是假设的)。因此,事实是,截至2012年,没有理由不将其定义为良好的行为,事实上,C++11将更多此类情况定义为良好的行为

a = foo(bar(1), bar(2)); // this is unspecified behaviour
这两个函数调用可以按任何顺序进行,但它们仍然是两个不同的函数调用。即使调用是内联的,也不允许机器指令重叠。实际上,它们确实有相当多的重叠,但优化器仅限于生成严格表现为函数调用分离的代码

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

对于标量i,不需要分离:从i获取、添加和后添加的cpu指令可以自由混合,优化器可以假装不知道左侧的i和右侧的i是相同的东西。当这个先决条件被违反时,不知道它会产生什么样的坏程序集。因此,未定义。

未定义行为和未指定行为之间存在巨大差异。未指定的行为是格式良好的(即合法的),但该标准让编译器供应商在实现方面有一定的自由度。未定义的行为是一种在语法上似乎是正确的暴行。将行为定义为“未定义”而不是平淡非法(C++编译器必须拒绝的)的主要原因是,有时未定义的行为很难诊断。我认为答案非常简单:它是未定义的行为,因为C早就定义了未定义的行为,而改变它基本上没有潜在的好处

这就指向了我认为更重要的问题:为什么C会做出这种未定义的行为

我不认为这有那么简单的答案。一种可能是简单的警告——知道在编写C标准时,C已经在很多机器上实现、部署和使用了。当时有相当数量的机器看起来像是我仍然看到的许多代码:一些最初设计只是作为个人实验的东西,工作得很好,最终被指定为“生产”,甚至没有通过最严重的问题来修复任何东西的象征性尝试。因此,即使没有人知道硬件会被破坏,也没有人能真正确定这样的硬件不存在,所以最安全的做法是将其称为UB,并将其处理掉

另一种可能性是,它超出了简单的谨慎。尽管我们对现代硬件感到相当安全,但当时可能有一些硬件,人们确实知道这些硬件会有重大问题,而且(特别是如果与该硬件相关的供应商在委员会中有代表的话),允许C在该硬件上运行被认为是重要的

还有一种可能性是,尽管没有人知道(甚至害怕)某些现有实现可能会被破坏,但他们预见到它将被破坏的未来可能性,因此未定义的行为被视为至少在某种程度上证明该语言未来的一种方式

最后一种可能性是,无论是谁编写了标准的这一部分,一旦他们提出了一套似乎可以接受的规则,就开始着手处理其他事情,尽管他们本可以提出至少有些人可能更喜欢的其他规则


如果让我猜的话,我会说这可能是我给出的第三种和第四种可能性的结合——委员会知道并行计算的发展,但不知道它最终会如何运行,所以无论是谁写的,在实现方面最大限度地提高自由度似乎是获得共识的最简单/最简单的途径,这样他们就可以完成它并继续做更大更好的事情。

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


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

看到一个关于这个主题的问题,这个问题已经在前面搜索过了:)真正的问题是,你到底为什么要写这些代码?既然想写这篇文章是疯狂的,为什么要写一些关于可能发生什么/不可能发生什么的规则来增加标准的复杂性呢