从 g++ 输出中删除不需要的汇编程序语句

Remove needless assembler statements from g++ output

我正在调查本地二进制文件的一些问题。我注意到 g++ 创建了很多对我来说似乎没有必要的 ASM 输出。 -O0 示例:

Derived::Derived():
    pushq   %rbp
    movq    %rsp, %rbp
    subq    , %rsp          <--- just need 8 bytes for the movq to -8(%rbp), why -16?
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi         <--- now we have moved rdi onto itself.
    call    Base::Base()
    leaq    16+vtable for Derived(%rip), %rdx
    movq    -8(%rbp), %rax     <--- effectively %edi, does not point into this area of the stack
    movq    %rdx, (%rax)       <--- thus this wont change -8(%rbp)
    movq    -8(%rbp), %rax     <--- so this statement is unnecessary
    movl    12, 12(%rax)
    nop
    leave
    ret

选项-O1 -fno-inline -fno-elide-constructors -fno-omit-frame-pointer:

Derived::Derived():
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    subq    , %rsp       <--- reserve some stack space and never use it.
    movq    %rdi, %rbx
    call    Base::Base()
    leaq    16+vtable for Derived(%rip), %rax
    movq    %rax, (%rbx)
    movl    12, 12(%rbx)
    addq    , %rsp       <--- release unused stack space.
    popq    %rbx
    popq    %rbp
    ret

此代码用于 Derived 的构造函数,该构造函数调用 Base 基本构造函数,然后覆盖位置 0 处的 vtable 指针,并将常量值设置为其持有的 int 成员Base 包含什么。

问题:

is there a reason the compiler cannot detect these cases with -O0 or -O1

正是因为您告诉编译器不要这样做。这些是 optimisation 级别,需要关闭或关闭以进行正确调试。您还在为 run-time.

权衡编译时间

您正在以错误的方式查看 telescope,请查看当您进行 up 优化时编译器将为您做的很棒的优化。

我在您的 -O1 输出中没有看到任何明显遗漏的优化。当然,除了将 RBP 设置为帧指针,但您使用 -fno-omit-frame-pointer 非常清楚,您知道为什么 GCC 没有优化它。

The function has no local variables

您的函数是一个非静态 class 成员函数,因此 它有一个隐式参数:this in rdi。由于 -O0,哪个 g++ 溢出到堆栈。函数参数算作局部变量。

How does a cyclic move without an effect improve the debugging experience. Please elaborate.

改进C/C++ 调试:调试信息格式只能描述 C 变量相对于 RSP 或 RBP 的位置,而不是它当前所在的寄存器。此外,您可以使用调试器 修改 任何变量并继续,获得预期的结果,就好像您在 C++ 抽象机中那样做一样。每个语句都被编译成一个单独的 asm 块,寄存器中没有任何值(有趣的事实:除了 register int foo:该关键字确实影响调试模式代码生成)。

Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? 也适用于 G++ 和其他编译器。

Which options would I have to set?

如果您正在阅读/调试 asm,请至少使用 -Og 或更高版本 来禁用调试模式溢出所有语句之间的行为-O0。最好是 -O2-O3 除非你喜欢看到比完全优化更多的错过的优化。但是 -Og-O1 将进行寄存器分配并进行合理的循环(底部有条件分支),以及各种简单的优化。虽然还不是异或归零的标准窥视孔

解释了如何编写带有 args 和 return 值的函数,这样您就可以编写不会优化掉的函数。

加载到 RAX 然后 movq %rax, %rdi 只是 -O0 的副作用。 GCC 花费很少的时间来优化程序逻辑的 GIMPLE and/or RTL 内部表示(在发出 x86 asm 之前),它甚至没有注意到它可以首先加载到 RDI 中。 -O0 的部分要点是快速编译,以及一致的调试。

Why is the subq , %rsp statement generated at all?

因为ABI要求call指令前16字节栈对齐,而这个函数做了偶数个8字节pushes . (call 本身推送一个 return 地址)。它会在 -O1 没有 -fno-omit-frame-pointer 时消失,因为你没有强迫 g++ 到 push/pop RBP 以及它实际需要的调用保留寄存器。

有趣的事实:clang 通常会使用虚拟 push %rcx/pop 或其他东西,具体取决于 -mtune 选项,而不是 8 字节子。

如果它是叶函数,g++ 将只使用 RSP 下面的红色区域供当地人使用,即使在 -O0Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets?


在未优化的代码中,G++ 分配它从未使用过的额外 16 个字节的情况并不少见。即使有时在启用优化的情况下,当以 16 字节为目标时,g++ 也会将其堆栈分配大小向上舍入太多。这是一个错过优化的错误。例如Memory allocation and addressing in Assembly