什么时候在编译过程中删除了无关紧要的代码(没有效果的代码)?

时间:2017-10-09 03:43:33

标签: c++ gcc assembly x86

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编译器不进行这些优化。

2 个答案:

答案 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应该是一个良好的开端。