Twi*_*ard 12 c++ memory stack-frame
假设我有一个函数,根据某些运行时条件创建昂贵的自动对象或创建便宜的自动对象:
void foo() {
if (runtimeCondition) {
int x = 0;
} else {
SuperLargeObject y;
}
}
Run Code Online (Sandbox Code Playgroud)
当编译器为此函数的堆栈帧分配内存时,它是否会分配足够的内存来存储SuperLargeObject,并且如果导致 的条件为int真,则额外的内存将不会被使用?或者它会以其他方式分配内存吗?
chr*_*nte 12
这取决于您的编译器和优化设置。在未优化的构建中,大多数 C++ 编译器可能会为两个对象分配堆栈内存,并根据采用的分支使用其中之一。在优化的构建中,事情变得更加有趣:
如果两个对象(theint和 the 都SuperLargeObject没有被使用,并且编译器可以证明构造SuperLargeObject没有副作用,则两个分配都将被省略。
如果对象逃逸该函数,即它们的地址被传递给另一个函数,则编译器必须为它们提供内存。但由于它们的生命周期不重叠,因此它们可以存储在重叠的内存区域中。这是否真的发生取决于编译器。
正如您在这里所看到的,不同的编译器为这两个函数生成不同的程序集:(来自OP和参考的修改示例,全部针对x86-64编译)
void escape(void const*);
struct SuperLargeObject {
char data[104];
};
void f(bool cond) {
if (cond) {
int x;
escape(&x);
}
else {
SuperLargeObject y;
escape(&y);
}
}
void g() {
SuperLargeObject y;
escape(&y);
}
Run Code Online (Sandbox Code Playgroud)
请注意,所有堆栈分配都是 8 的奇数倍,因为 x86-64 ABI 要求堆栈指针以 16 字节对齐,并且call返回地址的指令将推送 8 个字节(感谢 @PeterCordes 在 上向我解释了这一点)另一篇文章)。
f(bool):
sub rsp, 120
test dil, dil
lea rax, QWORD PTR [104+rsp]
lea rdx, QWORD PTR [rsp]
cmovne rdx, rax
mov rdi, rdx
call escape(void const*)
add rsp, 120
ret
g():
sub rsp, 104
lea rdi, QWORD PTR [rsp]
call escape(void const*)
add rsp, 104
ret
Run Code Online (Sandbox Code Playgroud)
ICC 似乎分配了足够的内存来存储两个对象,然后根据运行时条件(使用cmov)在两个非重叠区域之间进行选择,并将所选指针传递给转义函数。
在引用函数中,g它仅分配 104 个字节,恰好是SuperBigObject.
f(bool):
sub rsp, 120
mov rdi, rsp
call escape(void const*)
add rsp, 120
ret
g():
sub rsp, 120
mov rdi, rsp
call escape(void const*)
add rsp, 120
ret
Run Code Online (Sandbox Code Playgroud)
GCC 也分配 120 字节,但它将两个对象放置在同一地址,因此不发出任何cmov指令。
f(bool):
sub rsp, 104
test edi, edi
mov rdi, rsp
call escape(void const*)@PLT
add rsp, 104
ret
g():
sub rsp, 104
mov rdi, rsp
call escape(void const*)@PLT
add rsp, 104
ret
Run Code Online (Sandbox Code Playgroud)
Clang 还合并了两个分配,并将分配大小减少到必要的 104 字节。
不幸的是我不明白为什么它测试 function 中的条件f。
您还应该注意,当编译器可以将一个或两个变量放入寄存器中时,根本不会分配内存,即使它们在整个函数中使用和重新分配也是如此。对于int's 和long's 以及其他小对象来说,这是最常见的情况,如果它们的地址不转义该函数。