.NET中栈和堆的比较1
原文出處:
http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx
盡管在.NET framework下我們并不需要擔(dān)心內(nèi)存管理和垃圾回收(Garbage Collection),但是我們還是應(yīng)該了解它們,以優(yōu)化我們的應(yīng)用程序。同時(shí),還需要具備一些基礎(chǔ)的內(nèi)存管理工作機(jī)制的知識(shí),這樣能夠有助于解釋我們?nèi)粘3绦蚓帉懼械淖兞康男袨?。在本文中我將講解棧和堆的基本知識(shí),變量類型以及為什么一些變量能夠按照它們自己的方式工作。
在.NET framework環(huán)境下,當(dāng)我們的代碼執(zhí)行時(shí),內(nèi)存中有兩個(gè)地方用來(lái)存儲(chǔ)這些代碼。假如你不曾了解,那就讓我來(lái)給你介紹棧(Stack)和堆(Heap)。棧和堆都用來(lái)幫助我們運(yùn)行代碼的,它們駐留在機(jī)器內(nèi)存中,且包含所有代碼執(zhí)行所需要的信息。
* 棧vs堆:有什么不同?
棧負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)路徑,而堆則負(fù)責(zé)保存對(duì)象(或者說(shuō)數(shù)據(jù),接下來(lái)將談到很多關(guān)于堆的問(wèn)題)的路徑。
可以將棧想象成一堆從頂向下堆疊的盒子。當(dāng)每調(diào)用一次方法時(shí),我們將應(yīng)用程序中所要發(fā)生的事情記錄在棧頂?shù)囊粋€(gè)盒子中,而我們每次只能夠使用棧頂?shù)哪莻€(gè)盒子。當(dāng)我們棧頂?shù)暮凶颖皇褂猛曛?#xff0c;或者說(shuō)方法執(zhí)行完畢之后,我們將拋開這個(gè)盒子然后繼續(xù)使用棧頂上的新盒子。堆的工作原理比較相似,但大多數(shù)時(shí)候堆用作保存信息而非保存執(zhí)行路徑,因此堆能夠在任意時(shí)間被訪問(wèn)。與棧相比堆沒(méi)有任何訪問(wèn)限制,堆就像床上的舊衣服,我們并沒(méi)有花時(shí)間去整理,那是因?yàn)榭梢噪S時(shí)找到一件我們需要的衣服,而棧就像儲(chǔ)物柜里堆疊的鞋盒,我們只能從最頂層的盒子開始取,直到發(fā)現(xiàn)那只合適的。
[heapvsstack1.gif]
以上圖片并不是內(nèi)存中真實(shí)的表現(xiàn)形式,但能夠幫助我們區(qū)分棧和堆。
棧是自行維護(hù)的,也就是說(shuō)內(nèi)存自動(dòng)維護(hù)棧,當(dāng)棧頂?shù)暮凶硬辉俦皇褂?#xff0c;它將被拋出。相反的,堆需要考慮垃圾回收,垃圾回收用于保持堆的整潔性,沒(méi)有人愿意看到周圍都是贓衣服,那簡(jiǎn)直太臭了!
* 棧和堆里有些什么?
當(dāng)我們的代碼執(zhí)行的時(shí)候,棧和堆中主要放置了四種類型的數(shù)據(jù):值類型(Value Type),引用類型(Reference Type),指針(Pointer),指令(Instruction)。
1.值類型:
在C#中,所有被聲明為以下類型的事物被稱為值類型:
bool
byte
char
decimal
double
enum
float
int
long
sbyte
short
struct
uint
ulong
ushort
2.引用類型:
所有的被聲明為以下類型的事物被稱為引用類型:
class
interface
delegate
object
string
3.指針:
在內(nèi)存管理方案中放置的第三種類型是類型引用,引用通常就是一個(gè)指針。我們不會(huì)顯示的使用指針,它們由公共語(yǔ)言運(yùn)行時(shí)(CLR)來(lái)管理。指針(或引用)是不同于引用類型的,是因?yàn)楫?dāng)我們說(shuō)某個(gè)事物是一個(gè)引用類型時(shí)就意味著我們是通過(guò)指針來(lái)訪問(wèn)它的。指針是一塊內(nèi)存空間,而它指向另一個(gè)內(nèi)存空間。就像棧和堆一樣,指針也同樣要占用內(nèi)存空間,但它的值是一個(gè)內(nèi)存地址或者為空。
[heapvsstack2.gif]
4.指令:
在后面的文章中你會(huì)看到指令是如何工作的...
* 如何決定放哪兒?
這里有一條黃金規(guī)則:
1. 引用類型總是放在堆中。(夠簡(jiǎn)單的吧?)
2. 值類型和指針總是放在它們被聲明的地方。(這條稍微復(fù)雜點(diǎn),需要知道棧是如何工作的,然后才能斷定是在哪兒被聲明的。)
就像我們先前提到的,棧是負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)時(shí)的路徑。當(dāng)我們的代碼開始調(diào)用一個(gè)方法時(shí),將放置一段編碼指令(在方法中)到棧上,緊接著放置方法的參數(shù),然后代碼執(zhí)行到方法中的被“壓?!敝翖m?shù)淖兞课恢谩Mㄟ^(guò)以下例子很容易理解...
下面是一個(gè)方法(Method):
?????????? public int AddFive(int pValue)
????????? {
??????????????? int result;
??????????????? result = pValue + 5;
??????????????? return result;
????????? }
現(xiàn)在就來(lái)看看在棧頂發(fā)生了些什么,記住我們所觀察的棧頂下實(shí)際已經(jīng)壓入了許多別的內(nèi)容。
首先方法(只包含需要執(zhí)行的邏輯字節(jié),即執(zhí)行該方法的指令,而非方法體內(nèi)的數(shù)據(jù))入棧,緊接著是方法的參數(shù)入棧。(我們將在后面討論更多的參數(shù)傳遞)
[heapvsstack3.gif]
接著,控制(即執(zhí)行方法的線程)被傳遞到堆棧中AddFive()的指令上,
[heapvsstack4.gif]
當(dāng)方法執(zhí)行時(shí),我們需要在棧上為“result”變量分配一些內(nèi)存,
[heapvsstack5.gif]
The method finishes execution and our result is returned.
方法執(zhí)行完成,然后方法的結(jié)果被返回。
[heapvsstack6.gif]
通過(guò)將棧指針指向AddFive()方法曾使用的可用的內(nèi)存地址,所有在棧上的該方法所使用內(nèi)存都被清空,且程序?qū)⒆詣?dòng)回到棧上最初的方法調(diào)用的位置(在本例中不會(huì)看到)。
[heapvsstack7.gif]
在這個(gè)例子中,我們的"result"變量是被放置在棧上的,事實(shí)上,當(dāng)值類型數(shù)據(jù)在方法體中被聲明時(shí),它們都是被放置在棧上的。
值類型數(shù)據(jù)有時(shí)也被放置在堆上。記住這條規(guī)則--值類型總是放在它們被聲明的地方。好的,如果一個(gè)值類型數(shù)據(jù)在方法體外被聲明,且存在于一個(gè)引用類型中,那么它將被堆中的引用類型所取代。
來(lái)看另一個(gè)例子:
假如我們有這樣一個(gè)MyInt類(它是引用類型因?yàn)樗且粋€(gè)類類型):
????????? public class MyInt
????????? {?????????
???????????? public int MyValue;
????????? }
然后執(zhí)行下面的方法:
????????? public MyInt AddFive(int pValue)
????????? {
??????????????? MyInt result = new MyInt();
??????????????? result.MyValue = pValue + 5;
??????????????? return result;
????????? }
就像前面提到的,方法及方法的參數(shù)被放置到棧上,接下來(lái),控制被傳遞到堆棧中AddFive()的指令上。
[heapvsstack8.gif]
接著會(huì)出現(xiàn)一些有趣的現(xiàn)象...
因?yàn)?#34;MyInt"是一個(gè)引用類型,它將被放置在堆上,同時(shí)在棧上生成一個(gè)指向這個(gè)堆的指針引用。
[heapvsstack9.gif]
在AddFive()方法被執(zhí)行之后,我們將清空...
[heapvsstack10.gif]
我們將剩下孤獨(dú)的MyInt對(duì)象在堆中(棧中將不會(huì)存在任何指向MyInt對(duì)象的指針!)
[heapvsstack11.gif]
這就是垃圾回收器(后簡(jiǎn)稱GC)起作用的地方。當(dāng)我們的程序達(dá)到了一個(gè)特定的內(nèi)存閥值,我們需要更多的堆空間的時(shí)候,GC開始起作用。GC將停止所有正在運(yùn)行的線程,找出在堆中存在的所有不再被主程序訪問(wèn)的對(duì)象,并刪除它們。然后GC會(huì)重新組織堆中所有剩下的對(duì)象來(lái)節(jié)省空間,并調(diào)整棧和堆中所有與這些對(duì)象相關(guān)的指針。你肯定會(huì)想到這個(gè)過(guò)程非常耗費(fèi)性能,所以這時(shí)你就會(huì)知道為什么我們需要如此重視棧和堆里有些什么,特別是在需要編寫高性能的代碼時(shí)。
Ok... 這太棒了, 當(dāng)它是如何影響我的?
Good question.
當(dāng)我們使用引用類型時(shí),我們實(shí)際是在處理該類型的指針,而非該類型本身。當(dāng)我們使用值類型時(shí),我們是在使用值類型本身。聽起來(lái)很迷糊吧?
同樣,例子是最好的描述。
假如我們執(zhí)行以下的方法:
????????? public int ReturnValue()
????????? {
??????????????? int x = new int();
??????????????? x = 3;
??????????????? int y = new int();
??????????????? y = x;?????
??????????????? y = 4;?????????
??????????????? return x;
????????? }
我們將得到值3,很簡(jiǎn)單,對(duì)吧?
假如我們首先使用MyInt類
???? public class MyInt
????????? {
??????????????? public int MyValue;
????????? }
接著執(zhí)行以下的方法:
????????? public int ReturnValue2()
????????? {
??????????????? MyInt x = new MyInt();
??????????????? x.MyValue = 3;
??????????????? MyInt y = new MyInt();
??????????????? y = x;????????????????
??????????????? y.MyValue = 4;?????????????
??????????????? return x.MyValue;
????????? }
我們將得到什么?...??? 4!
為什么?...? x.MyValue怎么會(huì)變成4了呢?...? 看看我們所做的然后就知道是怎么回事了:
在第一例子中,一切都像計(jì)劃的那樣進(jìn)行著:
????????? public int ReturnValue()
????????? {
??????????????? int x = 3;
??????????????? int y = x;???
??????????????? y = 4;
??????????????? return x;
????????? }
[heapvsstack12.gif]
在第二個(gè)例子中,我們沒(méi)有得到"3"是因?yàn)樽兞?#34;x"和"y"都同時(shí)指向了堆中相同的對(duì)象。
????????? public int ReturnValue2()
????????? {
??????????????? MyInt x;
??????????????? x.MyValue = 3;
??????????????? MyInt y;
??????????????? y = x;???????????????
??????????????? y.MyValue = 4;
??????????????? return x.MyValue;
????????? }
[heapvsstack13.gif]
希望以上內(nèi)容能夠使你對(duì)C#中的值類型和引用類型的基本區(qū)別有一個(gè)更好的認(rèn)識(shí),并且對(duì)指針及指針是何時(shí)被使用的有一定的基本了解。在系列的下一個(gè)部分,我們將深入內(nèi)存管理并專門討論方法參數(shù)。
To be continued...
轉(zhuǎn)載于:https://www.cnblogs.com/tager/archive/2009/03/27/1422899.html
總結(jié)
以上是生活随笔為你收集整理的.NET中栈和堆的比较1的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 系统架构师学习笔记_第十一章(上)_连载
- 下一篇: Ibatis XML 配置文件注释引起错