【问题标题】:Too large (overaligned?) stack frame with GCC but not with Clang使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)
【发布时间】:2021-04-20 09:35:07
【问题描述】:

考虑这个简单的代码:

class X {
  int i_;
public:
  X();
};

void f() {
  X x;
}

f 的堆栈帧在 GCC 中是 32 字节长,这是不必要的长。返回地址和x 只需要12 字节,根据Linux/x86_64 ABI 应该需要16 字节对齐。使用 Clang,只分配了 16 个字节。为什么 GCC 需要这么多的堆栈空间?

GCC 组装:

f():
  sub   rsp, 24
  lea   rdi, [rsp+12]
  call  X::X()
  add   rsp, 24
  ret

Clang 汇编:

f():       
  push  rax
  mov   rdi, rsp
  call  X::X()
  pop   rax
  ret

两者都是-O2。现场演示:https://godbolt.org/z/bcrWW36on

【问题讨论】:

    标签: c++ gcc stack clang stack-frame


    【解决方案1】:

    迷人的兔子洞,我的分析已经改了三遍了。

    看来这确实是一个错过的优化。玩了一会儿,我发现了另一个错过的优化,这次是 clang:

    如果你真的是use the x object,那么Clang使用rbx缓存x的地址而不是重新计算,这意味着它需要跨函数保存rbx,这扩展了堆栈中的已用空间8 帧(从 12 到 20),将对齐的堆栈帧增加到 32,与 gcc 相同。

    从调试的角度来看,我更喜欢 clang 使用 sub rsp, 8 而不是 push rax 来为 x 分配内存,因此在 valgrind 中不会将内存标记为已初始化。

    GCC 组装:

    f():
        sub     rsp, 24
        lea     rdi, [rsp+12]
        call    X::X() [complete object constructor]
        lea     rdi, [rsp+12]
        call    g(X&)
        add     rsp, 24
        ret
    

    Clang 汇编:

    f():
        push    rbx
        sub     rsp, 16
        lea     rbx, [rsp + 8]
        mov     rdi, rbx
        call    X::X() [complete object constructor]
        mov     rdi, rbx
        call    g(X&)
        add     rsp, 16
        pop     rbx
        ret
    

    我已经通过using a 32 byte vector as a data member检查了gcc是否可能使用32字节堆栈对齐,并且gcc和clang都生成代码来对齐堆栈指针,并使用基指针来实现可变长度堆栈帧。不过,我不知道为什么 Clang 在这里为对象分配 64 个字节。

    GCC 组装:

    f():
        push    rbp
        mov     rbp, rsp
        and     rsp, -32
        sub     rsp, 32
        mov     rdi, rsp
        call    X::X() [complete object constructor]
        leave
        ret
    

    Clang 汇编:

    f():                                  # @f()
        push    rbp
        mov     rbp, rsp
        and     rsp, -32
        sub     rsp, 64
        mov     rdi, rsp
        call    X::X() [complete object constructor]
        mov     rsp, rbp
        pop     rbp
        ret
    

    如果不实际测量性能,很难判断哪个更好 -- -O2 将针对运行时进行优化,而不是针对堆栈帧大小进行优化,因此所有这些选择都可能有充分的理由。

    【讨论】:

      猜你喜欢
      • 2016-05-04
      • 2017-01-04
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      • 2020-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多