淺談C#中堆和棧的差別(附上圖解)。本站提示廣大學習愛好者:(淺談C#中堆和棧的差別(附上圖解))文章只能為提供參考,不一定能成為您想要的結果。以下是淺談C#中堆和棧的差別(附上圖解)正文
線程客棧:簡稱棧 Stack
托管堆: 簡稱堆 Heap
應用.Net框架開辟法式的時刻,我們無需關懷內存分派成績,由於有GC這個年夜管家給我們摒擋一切。假如我們寫出以下兩段代碼:
代碼段1:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
代碼段2:
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,譯注:挪用幀),接著下一個。堆則否則,像是一個倉庫,貯存著我們應用的各類對象等信息,跟棧分歧的是他們被挪用終了不會立刻被清算失落。
如圖1,棧與堆表示圖
(圖1)
棧內存無需我們治理,也不受GC治理。當棧頂元素應用終了,立馬釋放。而堆則須要GC(Garbage collection:渣滓搜集器)清算。
二,甚麼元素被分派到棧?甚麼被分派到堆?
當我們法式履行的時刻,在棧和堆平分配有四種重要的類型:值類型,援用類型,指針,指令。
值類型:
在C#中,繼續自System.ValueType的類型被稱為值類型,重要有以下幾種(CLR2.0中支撐類型有增長):
* bool
* byte
* char
* decimal
* double
* enum
* float
* int
* long
* sbyte
* short
* struct
* uint
* ulong
* ushort
援用類型:
以下是援用類型,繼續自System.Object:
* class
* interface
* delegate
* object
* string
指針:
在內存區中,指向一個類型的援用,平日被稱為“指針”,它是受CLR( Common Language Runtime:公共說話運轉時)治理,我們不克不及顯示應用。須要留意的是,一個類型的援用即指針跟援用類型是兩個完整分歧的概念。指針在內存中占一塊內存區,它自己只代表一個內存地址(或許null),它所指向的另外一塊內存區才是我們真實的數據或許類型。如圖2:
(圖2)
指令:
後文對指令再做引見。
三,若何分派?
我們先看一下兩個不雅點:
不雅點1,援用類型老是被分派在堆上。(准確?)
不雅點2,值類型和指針老是分派在被界說的處所,他們紛歧定被分派到棧上。(這個懂得起來有點難度,須要漸漸來)
上文說起的棧(Stack),在法式運轉的時刻,每一個線程(Thread)都邑保護一個本身的專屬線程客棧。
當一個辦法被挪用的時刻,主線程開端在所屬法式集的元數據中,查找被挪用辦法,然後經由過程JIT即時編譯並把成果(普通是當地CPU指令)放在棧頂。CPU經由過程總線從棧頂取指令,驅動法式以履行下去。
上面我們以實例來詳談。
照樣我們開篇所列的代碼段1:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
當AddFive辦法開端履行的時刻,辦法參數(parameters)則在棧上分派。如圖3:
(圖3)
留意:辦法其實不在棧中存活,圖示僅供參考。
接著,指令指向AddFive辦法外部,假如該辦法是第一次履行,起首要停止JIT即時編譯。如圖4:
(圖4)
當辦法外部開端履行的時刻,變量result被分派在棧上,如圖5:
(圖5)
辦法履行終了,並且辦法前往後,如圖6所示:
(圖6)
在辦法履行終了前往後,棧上的區域被清算。如圖7:
(圖7)
以上看出,一個值類型變量,普通會分派在棧上。那不雅點2中所述又做何懂得?“值類型和指針老是分派在被界說的處所,他們紛歧定被分派到棧上”。
緣由就是假如一個值類型被聲明在一個辦法體外而且在一個援用類型中,那它就會在堆長進行分派。
照樣代碼段2:
public class MyInt { public int MyValue; } public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; }
當線程開端履行AddFive辦法的時刻,參數被分派到棧上,如圖8所示:
(圖8)
因為MyInt是一個援用類型,所以它被分派到堆上,而且在棧中生成一個指針(result),如圖9:
(圖9)
AddFive辦法履行終了時的情形如圖10:
(圖10)
棧上內存被清算,堆中仍然存在,如圖11:
(圖11)
當法式須要更多的堆空間時,GC須要停止渣滓清算任務,暫停一切線程,找出一切弗成到達對象,即無被援用的對象,停止清算。並告訴棧中的指針從新指向地址排序後的對象。如今我們應當曉得,懂得棧和堆,對我們開辟出高機能法式的主要性。當我們應用援用類型的時刻,普通是對指針停止的操作而非援用類型對象自己。然則值類型則操作其自己。
接上去,我們用例子解釋這一點。
例1:
public int ReturnValue() { int x = new int(); x = 3; int y = new int(); y = x; y = 4; return x; }
履行成果為3,稍作修正:
例2:
public class MyInt { public int MyValue; } public int ReturnValue2() { MyInt x = new MyInt(); x.MyValue = 3; MyInt y = new MyInt(); y = x; y.MyValue = 4; return x.MyValue; }
履行成果為4。
我們來剖析下緣由,其實例1的跟以下代碼所起功效一樣:
public int ReturnValue() { int x = 3; int y = x; y = 4; return x; }
如圖12所示,在棧上x和y分離占用一塊內存區,互不攪擾。
(圖12)
而例2,與以下代碼所起功效一樣:
public int ReturnValue2() { MyInt x; x.MyValue = 3; MyInt y; y = x; y.MyValue = 4; return x.MyValue; }
如圖13所示,
(圖13)
棧上的指針x和y指向堆上統一個區域,修正其一必會轉變堆上的數據。