簡體   English   中英

C ++:編譯器如何知道為每個堆棧幀分配多少內存?

[英]C++: How does the compiler know how much memory to allocate for each stack frame?

這里的第一個答案中,提到了以下關於C ++中的堆棧內存:

調用函數時,會在堆棧頂部為區域變量和一些簿記數據保留一個塊。

這在頂層是完全有意義的,並且讓我好奇在給出這個問題的上下文時,在分配這個內存時,智能編譯器是如何分配這個內存的:因為大括號本身不是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)

通常:在調用某個函數之前,在確定新堆棧幀需要多少內存時,編譯器是否會考慮變量范圍? 如果這是特定於編譯器的,那么一些着名的編譯器如何做呢?

編譯器將根據需要分配空間(通常用於函數開頭的所有項),但不會為循環中的每次迭代分配空間。

例如,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次。 因為在任何給定時間只存在其中一個變量。

(編譯器可以為x和第二個myObject[1000]重用myObject[1000]的空間 - 並且可能會為優化的構建重用該空間,但在這種情況下,它也會完全刪除這些變量,因為它們沒有被使用,所以它不會很好地顯示)

在現代編譯器中,該函數首先轉換為流程圖。 在流程的每一個弧形,編譯器知道多少個變量是活的 -也就是拿着可見值。 其中一些將存在於寄存器中,而對於其他寄存器,編譯器將需要保留堆棧空間。

隨着優化器進一步參與,事情會變得更復雜,因為它可能不希望移動堆棧變量。 這不是免費的。

盡管如此,編譯器最終還是准備好了所有的匯編操作,並且可以計算使用了多少個唯一的堆棧地址。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM