我今天和我的一位同事讨论过,当启用了激进的优化时,编译器可能会改变程序的语义。
我的同事声明,在启用优化时,编译器可能会更改某些指令的顺序。那样:
function foo(int a, int b)
{
if (a > 5)
{
if (b < 6)
{
// Do something
}
}
}
可能会更改为:
function foo(int a, int b)
{
if (b < 6)
{
if (a > 5)
{
// Do something
}
}
}
当然,在这种情况下,不会改变程序的一般行为,而且确实非常重要。
根据我的理解,我相信两个if (condition)
属于两个不同的序列点并且编译器无法更改它们的顺序,即使更改它将保持相同的一般行为。
所以,亲爱的SO用户,关于这一点的真相是什么?
答案 0 :(得分:12)
如果编译器可以验证这两者之间没有可观察到的差异,那么可以自由地进行这样的优化。
序列点是一个概念性的东西:编译器必须生成代码,使其行为,好像遵循所有语义规则(如序列点)。生成的代码实际上不必遵循这些规则,如果不遵循它们就不会产生程序行为的可观察差异。
即使你有:
if (a > 5 && b < 6)
编译器可以自由地将其重新排列为
if (b < 6 && a > 5)
因为两者之间没有可观察到的差异(在a
和b
都是int
值的特定情况下)。 [这假设同时阅读a
和b
是安全的;如果读取其中一个可能会导致一些错误(例如,一个有陷阱值),那么编译器将在它可以进行的优化方面受到更多限制。]
答案 1 :(得分:11)
由于两个程序片段之间没有 observable 差异 - 假设实现是一个不使用陷阱值或其他可能导致内部比较的东西,而不仅仅是评估到true
或false
- 编译器可以在“仿佛”规则下优化一个到另一个。如果存在一些可观察到的差异或某种方式使符合程序的行为可能不同,那么如果编译器将一种形式更改为另一种形式,则编译器将不符合。
对于C ++,请参见1.9 [intro.execution] / 5.
执行格式良好的程序的一致实现应该产生与具有相同程序和相同输入的抽象机的相应实例的可能执行序列之一相同的可观察行为。但是,如果任何此类执行序列包含未定义的 操作时,本国际标准对使用该输入执行该程序的实现没有要求(甚至不考虑第一个未定义操作之前的操作)。
[此条款有时被称为“as-if”规则,因为只要结果,就好像已遵守要求,实施可以自由忽略本国际标准的任何要求,至于可以从程序的可观察行为中确定。例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序可观察行为的副作用。]
答案 2 :(得分:1)
是的,if
声明是sequence point。
然而,一个聪明且富有攻击性的编译器仍然可以重新排序不同的表达式,语句并改变序列点,不会出现副作用。
答案 3 :(得分:1)
序列点仅适用于抽象机器。
如果目标特定优化器可以证明反转两条指令的顺序没有副作用,它可以随意更改它们。
答案 4 :(得分:0)
完整表达式的结束(包括控制逻辑结构的那些,如if,while等等)是一个序列点。但是,序列点实际上只能保证先前评估的语句的副作用已经完成。
如果一个语句没有可观察到的副作用,编译器就可以做到最好的感觉。
答案 5 :(得分:0)
事实是,如果a> 5是假的,而b <6则是假的,反之亦然,那么序列将产生非常小的差异,因为它必须在更多场合计算两个条件。
实际上虽然它是如此微不足道,但在这种特殊情况下不值得打扰。
在某些情况下,它实际上确实有所作为,即当您在多个条件上过滤大量数据时必须先决定应用哪个过滤器,特别是如果其中只有一个是O(log N)或常数和随后的检查通过剩下的内容呈线性关系。
答案 6 :(得分:0)
很多PC程序员回复=)
如果在快速访问的寄存器中将“b”传递给函数,而在堆栈上传递“a”,编译器可能并且可能会优化速度的序列点。对于8位和16位MCU的许多编译器来说,这是一个很常见的情况:s。
通过优化,它不需要首先堆叠“b”,然后将“a”加载到寄存器中,然后计算“a”,然后将“b”加载回寄存器,然后计算“b”。相当混乱我宁愿希望通过重新排列序列点来处理编译器。
尽管如前所述,为了符合标准,编译器需要确保它不会通过优化改变程序行为。