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

最佳答案

clang将在add eax, 0处发出-O0,但是gcc,ICC或MSVC都不会发出。见下文。
gcc -O0并不意味着“没有优化”。 gcc没有尝试将每个C表达式的每个组件直接音译为asm指令的“braindead文字翻译”模式。

GCC的-O0并非旨在完全未优化。它旨在“快速编译”,并使调试产生预期的结果(即使您使用调试器修改C变量,或跳转到函数中的另一行)。因此,它会溢出/重新加载每个C语句周围的所有内容,假设可以通过在此类块之前停止的调试器来异步修改内存。 (结果的有趣示例,以及更详细的解释:Why does integer division by -1 (negative one) result in FPE?)

不需要gcc -O0来制作甚至更慢的代码(例如,忘记0是可加性标识),因此没有人为此实现选项。如果该行为是可选的,甚至可能会使gcc变慢。 (或者也许有这样的选项,但是即使在-O0上,它也是默认启用的,因为它速度快,不会损害调试且有用。通常人们喜欢它的调试版本可以足够快地运行以使其可用,特别是对于大型或实际的情况时间项目。)

正如@Basile Starynkevitch在Disable all optimization options in GCC中解释的那样,gcc在生成可执行文件的过程中始终会转换其内部表示形式。完全执行此操作会导致某些优化。

例如,gcc的“除以常数”算法even at -O0 使用a fixed-point multiplicative inverse或移位(对于2的幂),而不是idiv指令。但是clang -O0idiv使用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在 Debug模式下通常是非常文字的,但是即使避免发出add eax,0,它也是如此。

Clang也是Godbolt上唯一将idiv用作return x/2;的4个x86编译器之一。其余全部为SAR + CMOV或任何实现C的带符号划分语义的东西。

09-10 04:43
查看更多