使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)
Posted
技术标签:
【中文标题】使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)【英文标题】:Too large (overaligned?) stack frame with GCC but not with 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
【问题讨论】:
【参考方案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
将针对运行时进行优化,而不是针对堆栈帧大小进行优化,因此所有这些选择都可能有充分的理由。
【讨论】:
以上是关于使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)的主要内容,如果未能解决你的问题,请参考以下文章
没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译
两级嵌套 c++ 类适用于 GCC,但使用 Clang 失败