C++:编译器如何知道为每个堆栈帧分配多少内存?
Posted
技术标签:
【中文标题】C++:编译器如何知道为每个堆栈帧分配多少内存?【英文标题】:C++: How does the compiler know how much memory to allocate for each stack frame? 【发布时间】:2016-07-07 20:35:41 【问题描述】:在第一个答案here中,提到了C++中的栈内存:
当一个函数被调用时,一个块被保留在栈顶用于局部变量和一些簿记数据。
考虑到this question 的上下文,这在顶层非常有意义,并且让我很好奇编译器在分配内存时的智能编译器本身是什么:因为大括号本身不是 C 中的堆栈帧 (我假设这也适用于 C++),我想检查编译器是否根据单个函数中的变量范围优化保留内存。
在下面我假设堆栈在函数调用之前看起来像这样:
--------
|main()|
-------- <- stack pointer: space above it is used for current scope
| |
| |
| |
| |
--------
然后在调用函数f()
后如下:
--------
|main()|
-------- <- old stack pointer (osp)
| f() |
-------- <- stack pointer, variables will now be placed between here and osp upon reaching their declarations
| |
| |
| |
| |
--------
例如,给定这个函数
void f()
int x = 0;
int y = 5;
int z = x + y;
大概,这只会分配3*sizeof(int)
+ 一些额外的记账开销。
但是,这个函数呢:
void g()
for (int i = 0; i < 100000; i++)
int x = 0;
MyObject myObject[1000];
MyObject myObject[1000];
忽略编译器优化可能会忽略上面的很多东西,因为它们实际上什么都不做,我对第二个示例中的以下内容感到好奇:
对于for
循环:堆栈空间是否足以容纳所有 100000 个整数?
除此之外,堆栈空间是否会包含1000*sizeof(MyObject)
或2000*sizeof(MyObject)
?
一般来说:在调用某个函数之前,编译器在确定新堆栈帧需要多少内存时是否考虑变量范围?如果这是特定于编译器的,那么一些知名的编译器是如何做到的?
【问题讨论】:
一对
是一个作用域。循环对x
重复使用相同的内存,两个myObject
数组不会同时存在。
为什么要为100000
ints分配空间,当它可以重用相同的空间时?数组也是如此。
编译器检查函数的每个作用域,保留的空间是所有作用域中可以同时存在的最大空间。
堆栈空间是预先分配的,编译器只是使用它,直到它用完并且你有溢出。
@n.m.同时,并非所有关于 C++ 的问题都只需要询问语言。询问编译器的实现细节,或者只是询问编译器通常如何处理语言特性的一般原则,也可以。
【参考方案1】:
编译器将根据需要分配空间(通常为函数开头的所有项),但不会为循环中的每次迭代分配空间。
例如,Clang 生成的内容,如 LLVM-IR
define void @_Z1gv() #0
%i = alloca i32, align 4
%x = alloca i32, align 4
%myObject = alloca [1000 x %class.MyObject], align 16
%myObject1 = alloca [1000 x %class.MyObject], align 16
store i32 0, i32* %i, align 4
br label %1
; <label>:1: ; preds = %5, %0
%2 = load i32, i32* %i, align 4
%3 = icmp slt i32 %2, 100000
br i1 %3, label %4, label %8
; <label>:4: ; preds = %1
store i32 0, i32* %x, align 4
br label %5
; <label>:5: ; preds = %4
%6 = load i32, i32* %i, align 4
%7 = add nsw i32 %6, 1
store i32 %7, i32* %i, align 4
br label %1
; <label>:8: ; preds = %1
ret void
这是以下结果:
class MyObject
public:
int x, y;
;
void g()
for (int i = 0; i < 100000; i++)
int x = 0;
MyObject myObject[1000];
MyObject myObject[1000];
因此,如您所见,x
仅分配一次,而不是 100000 次。因为在任何给定时间只会存在这些变量中的一个。
(编译器可以将myObject[1000]
的空间用于x
和第二个myObject[1000]
- 并且可能会为优化构建这样做,但在这种情况下,它也会完全删除这些变量,因为它们不是用过,所以显示不太好)
【讨论】:
就堆栈指针而言:在达到g()
时它会增加max(2*sizeof(int), 1000*sizeof(MyObject))
吗?因为只有那些变量可以同时存在。我认为大会并不清楚这一点。
很可能,是的,但它可能是所有局部变量的总和 - 几乎可以肯定是在未优化的构建中[这是我的代码显示的]
当然,在优化的构建中,i
和 x
很可能驻留在寄存器中而不是堆栈中。【参考方案2】:
在现代编译器中,函数首先被转换为流程图。在流程的每一条弧线中,编译器都知道有多少变量是live——也就是说,它拥有一个可见的值。其中一些将存在于寄存器中,而对于其他一些,编译器将需要保留堆栈空间。
随着优化器的进一步参与,事情变得有点复杂,因为它可能不喜欢移动堆栈变量。这不是免费的。
不过,最终编译器已经准备好所有的汇编操作,并且可以只计算使用了多少个唯一的堆栈地址。
【讨论】:
以上是关于C++:编译器如何知道为每个堆栈帧分配多少内存?的主要内容,如果未能解决你的问题,请参考以下文章
(转载)C++内存分配方式详解——堆栈自由存储区全局/静态存储区和常量存储区