volatile int num = 0;
num = num + 10;
上面的C ++代码似乎在intel汇编中产生以下代码:
mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 10
mov DWORD PTR [rbp-4], eax
如果我将C ++代码更改为
volatile int num = 0;
num = num + 0;
为什么编译器不会将汇编代码生成为:
mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 0
mov DWORD PTR [rbp-4], eax
gcc7.2 -O0
leaves out the add eax, 0
, but all the other instructions are the same (Godbolt)
编译过程的哪一部分会删除这种简单的代码。是否有任何编译器标志会使GCC编译器不进行这些优化。
答案 0 :(得分:2)
clang将在add eax, 0
发出-O0
,但gcc,ICC和MSVC都不会。见下文。
gcc -O0
并不代表"没有优化"。 gcc没有" braindead字面翻译"模式,它尝试将每个C表达式的每个组件直接音译为asm指令。
GCC' -O0
并非完全未经优化。它的目的是"快速编译"并使调试给出预期的结果(即使您使用调试器修改C变量,或跳转到函数内的不同行)。因此,它会溢出/重新加载每个C语句周围的所有内容,假设在这样的块之前停止的调试器可以异步修改内存。 (有趣的后果示例,以及更详细的解释:Why does integer division by -1 (negative one) result in FPE?)
gcc -O0
要求更慢的代码(例如忘记0
是附加标识),所以没有人需要,所以没有人为此实现选项。如果该行为是可选的,它甚至可能使gcc变慢。 (或者也许有这样一个选项,但默认情况下即使在-O0
也是如此,因为它速度快,不会影响调试,也很有用。通常人们会喜欢它们debug build运行得足够快,可以使用,特别是对于大型或实时项目。)
正如@Basile Starynkevitch在Disable all optimization options in GCC中解释的那样,gcc总是在制作可执行文件的过程中通过其内部表示进行转换。只是这样做会导致某种优化。
例如,even at -O0
,gcc' s"除以常数"算法使用a fixed-point multiplicative inverse或shift(对于2的幂)而不是idiv
指令。但clang -O0
会将idiv
用于x /= 2
。
在这种情况下,Clang的-O0
优于gcc:
void foo(void) {
volatile int num = 0;
num = num + 0;
}
asm output on Godbolt for x86-64
push rbp
mov rbp, rsp
# your asm block from the question, but with 0 instead of 10
mov dword ptr [rbp - 4], 0
mov eax, dword ptr [rbp - 4]
add eax, 0
mov dword ptr [rbp - 4], eax
pop rbp
ret
正如你所说,gcc遗漏了无用的add eax,0
。 ICC17多次存储/重新加载。 MSVC在调试模式下通常非常直观,但即使它避免发出add eax,0
。
Clang也是Godbolt上4个x86编译器中唯一一个将idiv
用于return x/2;
的编译器。其他所有SAR + CMOV或任何实现C签名分区语义的东西。
答案 1 :(得分:0)
根据C ++中的“as if”规则,只要可观察行为与标准匹配,就可以自由地允许实现做任何想做的事情。具体来说,在C++17, 4.6/1
中(作为一个例子):
...符合实现需要模拟(仅)抽象机器的可观察行为,如下所述。
这项规定有时被称为“假设”规则,因为只要结果好像符合要求,实施可以自由地忽视本国际标准的任何要求,只要可以从该计划的可观察行为。
例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且不会产生影响程序的可观察行为的副作用。
至于如何控制gcc
,我的第一个建议是使用-O0
标志关闭所有优化。您可以使用各种-f<blah>
选项获得更精细的控制,但-O0
应该是一个良好的开端。