GCC 和 Clang 奇怪且不必要的堆栈使用[重复]

问题描述 投票:0回答:1

我正在使用 Compiler Explorer,注意到 GCC 和 Clang 在编译这个简单函数时会发出看似不必要的与堆栈相关的指令(Compiler Explorer)。

void bar(void);

int foo(void) {
    bar();
    return 42;
}

这是编译的结果(也可以通过上面的链接在编译器资源管理器中看到)。

-mabi=sysv
对输出程序集没有影响,但我想排除 ABI 作为奇怪程序集的原因。

// Expected output:
foo:
        call    bar
        mov     eax, 42
        ret

// gcc -O3 -mabi=sysv
// Why is it reserving unused space in the stack frame?
foo:
        sub     rsp, 8
        call    bar
        mov     eax, 42
        add     rsp, 8
        ret

// clang -O3 -mabi=sysv
// Why is it preserving a scratch register then moving it to another unused scratch register?
foo:
        push    rax
        call    bar@PLT
        mov     eax, 42
        pop     rcx
        ret

尽管函数不使用堆栈,但为什么堆栈帧会被修改?

我发现这特别奇怪,因为对于 GCC 和 Clang 等主要编译器来说,在使用已知的 ABI 时,这似乎是一个特别容易执行的优化。

我有一些理论,但我希望得到一些澄清。

  • 也许这样做是为了防止
    bar
    递归调用
    foo
    时出现无限循环?通过在每次调用时消耗少量的堆栈空间,我们确保程序在耗尽堆栈空间时最终会出现段错误。也许 clang 正在做同样的事情,但它使用
    push
    pop
    在某些情况下允许更好的管道?如果是这种情况,我可以使用任何 CLI 参数来禁用此行为吗?然而,这似乎不是问题,因为
    call
    在 x86-64 上无论如何都会将
    rip
    推送到堆栈。
  • 也许 C 或 AMD64 System V ABI 有一些我不知道的怪癖?
  • 也许我想得太多了,奇怪的汇编只是寄存器/堆栈优化不佳的结果。也许在编译过程中的某个时刻使用了堆栈,但是在优化使用之后,它无法删除堆栈上的值。
assembly gcc optimization clang x86-64
1个回答
0
投票

对齐。

call
指令将8个字节压入堆栈(返回地址)。因此,优化后的函数又调整了 8 个字节,以确保堆栈指针是 16 字节对齐的。

我相信这是 ABI 的要求,以确保 128 位 SSE 寄存器值可以溢出到自然对齐的地址,这对于避免性能下降或故障非常重要,具体取决于 CPU 配置。

clang 和 gcc 的情况实际上是相同的 - 你并不真正关心写入堆栈槽的内容,或者更新了哪个易失性寄存器,只关心堆栈指针被调整。

© www.soinside.com 2019 - 2024. All rights reserved.