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)) 吗?因为只有那些变量可以同时存在。我认为大会并不清楚这一点。 很可能,是的,但它可能是所有局部变量的总和 - 几乎可以肯定是在未优化的构建中[这是我的代码显示的] 当然,在优化的构建中,ix 很可能驻留在寄存器中而不是堆栈中。【参考方案2】:

在现代编译器中,函数首先被转换为流程图。在流程的每一条弧线中,编译器都知道有多少变量是live——也就是说,它拥有一个可见的值。其中一些将存在于寄存器中,而对于其他一些,编译器将需要保留堆栈空间。

随着优化器的进一步参与,事情变得有点复杂,因为它可能不喜欢移动堆栈变量。这不是免费的。

不过,最终编译器已经准备好所有的汇编操作,并且可以只计算使用了多少个唯一的堆栈地址。

【讨论】:

以上是关于C++:编译器如何知道为每个堆栈帧分配多少内存?的主要内容,如果未能解决你的问题,请参考以下文章

在编译时避免变长堆栈数组

动态创建二维数组使用多少大小

(转载)C++内存分配方式详解——堆栈自由存储区全局/静态存储区和常量存储区

C++中内存分配问题

如何找出 Linux 上的 GNU C++ 中剩余的可用内存量

垃圾回收机制与内存分配