天天看點

C#堆和棧的差別

轉載:https://www.cnblogs.com/qingtianMo/p/5255121.html

解釋1、

棧是編譯期間就配置設定好的記憶體空間,是以你的代碼中必須就棧的大小有明确的定義;堆是程式運作期間動态配置設定的記憶體空間,你可以根據程式的運作情況确定要配置設定的堆記憶體的大小

解釋2、

存放在棧中時要管存儲順序,保持着先進後出的原則,他是一片連續的記憶體域,有系統自動配置設定和維護。

而堆是無序的,他是一片不連續的記憶體域,有使用者自己來控制和釋放,如果使用者自己不釋放的話,當記憶體達到一定的特定值時,通過垃圾回收器(GC)來回收。

引用類型總是存放在堆中。

值類型和指針總是放在它們被聲明的地方。

調用方法:系統先将一段編碼(堆的首部位址)放到棧上,緊接着放置方法的參數。然後代碼執行到方法時,查找棧中放該堆首部位址的所有參數,并通過堆的首部位址來控制堆。

引用類型:總是放在堆當中。

當我們使用引用類型時,實際上隻是在處理該類型的指針。而非引用類型本身,使用值類型的話則是使用其本身。

解釋3.

線程堆棧:簡稱棧 Stack

托管堆: 簡稱堆 Heap

使用.Net架構開發程式的時候,我們無需關心記憶體配置設定問題,因為有GC這個大管家給我們料理一切。如果我們寫出如下兩段代碼:

public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}
           
public class MyInt
{
public int MyValue;
}
  
public MyInt AddFive(int pValue)
{
MyInt result = new MyInt();
result.MyValue = pValue + 5;
return result;
}
           

問題1:你知道代碼段1在執行的時候,pValue和result在記憶體中是如何存放,生命周期又如何?代碼段2呢?

要想釋疑以上問題,我們就應該對.Net下的棧(Stack)和托管堆(Heap)(簡稱堆)有個清楚認識,本立而道生。如果你想提高程式性能,了解棧和堆,必須的!

本文就從棧和堆,類型變量展開,對我們寫的程式進行庖丁解牛。

C#程式在CLR上運作的時候,記憶體從邏輯上劃分兩大塊:棧,堆。這倆基本元素組成我們C#程式的運作環境。

一,棧 vs 堆:差別?

棧通常儲存着我們代碼執行的步驟,如在代碼段1中 AddFive()方法,int pValue變量,int result變量等等。

而堆上存放的則多是對象,資料等。

我們可以把棧想象成一個接着一個疊放在一起的盒子(越高記憶體位址越低)。當我們使用的時 候,每次從最頂部取走一個盒子,當一個方法(或類型)被調用完成的時候,就從棧頂取走(called a Frame,譯注:調用幀),接着下一個。

棧記憶體無需我們管理,也不受GC管理。當棧頂元素使用完畢,立馬釋放。而堆則需要GC(Garbage collection:垃圾收集器)清理。

堆則不然,像是一個倉庫,儲存着我們使用的各種對象等資訊,跟棧不同的是他們被調用完畢不會立即被清理掉。

如圖1,棧與堆示意圖

C#堆和棧的差別

二,引用和值類型如何配置設定?

我們先看一下兩個觀點:

觀點1,引用類型總是被配置設定在堆上。(正确?)

觀點2,值類型和指針總是配置設定在被定義的地方,他們不一定被配置設定到棧上。

上文提及的棧(Stack),在程式運作的時候,每個線程(Thread)都會維護一個自己的專屬線程堆棧。

當一個方法被調用的時候,主線程開始在所屬程式集的中繼資料中,查找被調用方法,然後通過JIT即時編譯并把結果(一般是本地CPU指令)放在棧頂。CPU通過總線從棧頂取指令,驅動程式以執行下去。

下面我們以執行個體來詳談。

還是我們開篇所列的代碼段1:

public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}
           

當AddFive方法開始執行的時候,方法參數(parameters)則在棧上配置設定。如圖3:

C#堆和棧的差別

注意:方法并不在棧中存活,圖示僅供參考。

接着,指令指向AddFive方法内部,如果該方法是第一次執行,首先要進行JIT即時編譯。如圖4:

C#堆和棧的差別

方法執行完畢,而且方法傳回後,如圖6所示:

C#堆和棧的差別

在方法執行完畢傳回後,棧上的區域被清理。如圖7:

C#堆和棧的差別

現在可以回答第一個問題了 : 很明顯 pValue 與 result 都是被配置設定在stack上的,而且生命周期為這個函數的生命周期

以上看出,一個值類型變量,一般會配置設定在棧上。那觀點2中所述又做何了解?“值類型和指針總是配置設定在被定義的地方,他們不一定被配置設定到棧上”。

原因就是如果一個值類型被聲明在一個方法體外并且在一個引用類型中,那它就會在堆上進行配置設定。

還是代碼段2:

public class MyInt
{
public int MyValue;
}
  
public MyInt AddFive(int pValue)
{
MyInt result = new MyInt();
result.MyValue = pValue + 5;
return result;
}
           

剛開是的時候和代碼段一是一樣的,先找到方法,然後定義參數 pValue

C#堆和棧的差別

接下來就不一樣了,要定義一個引用類型

MyInt result = new MyInt();

new Myint()将會出現在托管堆中,而result被定義在堆棧中,其内容是指向 new MyInt()的位址,如下如所示

C#堆和棧的差別

AddFive方法執行完畢後 stack将被清理,而heap将會被保留一段時間,這一段時間示情況而定,如果沒有任何引用指向MyInt 垃圾管理器将會在合适的時候(不确定的時間)處理它

C#堆和棧的差別

這樣就能很好的回答問題一了

值類型嵌套引用類型 : 和代碼段二的了解方式一緻,值類型将會被配置設定在stack上,二其内部的引用将會在heap上被聲明.

引用類型嵌套值類型:如上圖的郵編堆,都會被聲明在heap上