是否存在算术运算受编译器优化影响的情况?

时间:2016-05-16 05:19:14

标签: c++ c compilation compiler-optimization

这是一个普遍的问题,但由于我主要处理gcc / g ++ / VStudio,我将其标记为c / c ++。当我搞乱优化选项时,我想到了这个问题。在最简单的形式中,考虑一个算术运算,如i / 6 * 8。如果一个人面对这个表达,他很可能将它简化为i / 3 * 4之类的东西。如果他更习惯乘以4,他将首先这样做,即(i * 4) / 3。我必须再次强调,这只是一个简单的例子。

那么编译器呢?他们是否有可能对此类操作采取相同措施?而且由于我们知道在上面的例子中,如果i是一个整数,那么简化和改变操作的顺序可能会导致完全不同的结果,问题可以改为:编译器是否完全避免这样的操作?

如果我们希望程序完全按照我们的说法进行一些算术运算而不改变运算顺序,那么我们是否应该担心编译器的行为?

4 个答案:

答案 0 :(得分:6)

最有可能的是,编译器会应用'常量折叠'和#'不断传播'对常数表达的优化。

在上面的例子中,编译器不能应用这种优化。

想象一下

i = i * (4/2)

编译器会生成

i= i * 2

这是因为不断折叠。

答案 1 :(得分:5)

编译器在优化代码方面非常保守。它们可能会改变执行操作的顺序,甚至可以预先计算操作数在编译时已知的算术运算(这称为常量折叠),但它们永远不会改变计算结果。 浮点运算有点问题。在不更改计算结果的情况下,通常无法更改计算顺序或预先计算。因此,大多数编译器默认情况下保留原样。然而,有可能要求编译器积极优化;在这种情况下,计算结果可能会改变,但用户要求它。例如,这是gcc选项-Ofast的情况(因为它在内部设置了选项-ffast-math)。请注意,它可能会导致奇怪的副作用,例如由零意外“随机”划分。

**编辑:关于非算术运算的注释**

当代码包含指针和函数调用时,优化变得更加困难。一般来说,预测副作用是不可能的(想想指针别名和全局变量)。因此编译器总是以非常保守的方式放弃:一个好的编译程序至少应该是正确的,快速是一种奢侈。

**编辑:一些例子**

这个SO问题给出了一个非常详细的例子,说明浮点可能发生什么:Different floating point result with optimization enabled - compiler bug?

答案 2 :(得分:4)

方程简化和编译器优化之间几乎没有共同之处。前者旨在使表达对人类更具可读性,后者旨在使程序尽可能高效。像你所做的那样简化方程式不会产生更快的程序,因此编译器不会为此烦恼。

编译器无法将表达式重新排序为i * 8 / 6,因为这可能会改变代码的含义。基本上,编译器比人类数学家聪明得多,因为编译器完全了解类型,而人类可能缺乏这种意识。编程时,i / 6 * 8 等同于i * 8 / 6!因为存在潜在的整数溢出问题。如果编译器不知道i将具有什么值,那么如果i * 8不能适合整数,则重新排序可能会导致溢出。

出于同样的原因,编译器也无法将代码更改为i / 3 * 4。如果程序员想要溢出怎么办?该程序可能是尝试演示未定义的行为,或者可能存在针对溢出情况实现的编译器行为。如果编译器会更改值,则可能不再存在溢出,并且程序行为将被更改,这是不允许的。

更有可能的是,编译器将寻找一种方法来通过在编译时预先计算它来删除其中一个操作。它也许也会寻找一种用位移替换除法的方法,因为除法传统上是一个缓慢的操作。实际要做的优化取决于整个周围的代码。

答案 3 :(得分:3)

正如其他答案所解释的那样,有效的编译器必须保守,不得采用任何可能改变定义良好的程序行为的优化。但重要的是要记住,这种保守主义仅适用于有效的,写得恰当,定义明确的程序。如果编译的代码依赖于未定义的行为,那么现代编译器在他们使用的优化中可能是彻头彻尾的激进,而在现实世界中,这意味着所述问题的答案实际上是,&#34; 是< / strong>,有些情况下算术运算会受到编译器优化的影响。&#34;

这里有两个很棒的网页,描述了编译器在面对未定义的行为时有时会应用的一些改变意义的优化:

编程语言定义通常被描述为&#34;合同&#34;程序员和程序之间作为一方,编译器及其实现者作为另一方。只要您的代码遵循所有规则,编译器就有义务生成一个可执行文件,其行为与语言定义和&#34;抽象机器&#34;完全匹配。但是如果你破坏了任何规则,特别是如果你的代码徘徊在任何未定义的行为中,所有的赌注都会被取消,合同就会失效,编译器基本上可以做任何想做的事。

例如,如果你写

int i = 1;
printf("%d\n", i++ + i++);    /* WRONG */

您很可能会发现在更改优化级别时表达式的值会发生变化。

(不用说,故事的寓意是,&#34;如果您编写未定义的代码,您必须小心使用哪种优化设置。&# 34;正确的经验教训是,“不要编写依赖于未定义行为的代码。&#34;)