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

C++ 是否存在算术运算受编译器优化影响的情况?,c++,c,compilation,compiler-optimization,C++,C,Compilation,Compiler Optimization,这是一个一般性问题,但由于我主要处理gcc/g++/VStudio,所以我将其标记为c/c++。当我在胡闹优化选项时,我想到了这个问题。在最简单的形式中,考虑一个算术运算,如 i/6×8 。如果一个人面对这个表情,他很可能会把它简化成类似于i/3*4。如果他更喜欢乘以4,他会首先这样做,即(i*4)/3。我必须再次强调,这只是一个简单的例子 那么编译器呢?他们是否有可能对此类操作进行同样的操作?既然我们知道在上面的例子中,如果i是一个整数,那么简化和更改操作顺序可能会导致完全不同的结果,那么问题

这是一个一般性问题,但由于我主要处理gcc/g++/VStudio,所以我将其标记为c/c++。当我在胡闹优化选项时,我想到了这个问题。在最简单的形式中,考虑一个算术运算,如<代码> i/6×8 。如果一个人面对这个表情,他很可能会把它简化成类似于
i/3*4
。如果他更喜欢乘以4,他会首先这样做,即
(i*4)/3
。我必须再次强调,这只是一个简单的例子

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


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

最有可能的情况是,编译器会对常量表达式应用“常量折叠”和“常量传播”优化

在上述情况下,编译器无法应用此类优化

想象

i = i * (4/2)
编译器将生成

i= i * 2
这是因为不断的折叠。

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

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

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

**编辑:一些例子**


这个问题给出了一个非常详细的例子,说明了浮点运算会发生什么:

等式简化和编译器优化之间几乎没有共同点。前者的目的是使表达式更易于人类阅读,后者的目的是使程序尽可能高效。像您所做的那样简化等式不会产生更快的程序,因此编译器不会为此烦恼

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

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


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

正如其他答案所解释的,有效的编译器必须是保守的,并且不得采用任何会改变定义良好的程序行为的优化。但重要的是要记住,这种保守主义只适用于有效的、正确编写的、定义良好的程序。如果正在编译的代码依赖于未定义的行为,那么现代编译器所采用的优化可能是彻底的激进,在现实世界中,这意味着对所述问题的答案实际上是,“,在某些情况下,算术运算可能会受到编译器优化的影响。”

以下两个伟大的网页描述了编译器在面对未定义行为时有时会应用的一些改变意义的优化:

  • “”

  • “”

编程语言定义通常被描述为程序员和程序作为一方,编译器及其实现者作为另一方之间的“契约”。只要代码遵循所有规则,编译器就有义务生成行为与语言定义和“抽象机器”完全匹配的可执行文件。但是如果您违反了任何规则,特别是如果您的代码出现了任何未定义的行为,那么所有的赌注都将被取消,契约将无效,编译器基本上可以做任何它想做的事情

例如,如果你写

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