值类型与引用类型比较与区别
在.NET中或許我們不用擔(dān)心內(nèi)存管理以及垃圾回收器(Garbage Collection GC)的問題,但是我們還是應(yīng)該了解這些東東以便在必要的時(shí)候優(yōu)化我們程序的性能。而且,如果對內(nèi)存管理如何工作有所了解,那將有助于解釋我們每個(gè)程序里的每個(gè)變量的運(yùn)行規(guī)律。這篇文章主要內(nèi)容是解釋堆(Heap)和棧(Stack),各種變量以及這些變量到底是如何工作的。
.Net Framework 在執(zhí)行代碼時(shí),有兩個(gè)用來存儲(chǔ)對象的地方,也就是堆和棧,用于幫助執(zhí)行我們的代碼。它們駐留在機(jī)器內(nèi)存中,包含了所有我們需要實(shí)現(xiàn)的信息。
Stack VS Heap
棧多多少少用來負(fù)責(zé)跟蹤你的代碼里正在執(zhí)行什么,或者說代碼里的什么東東被called。而堆則或多或少用來跟蹤我們的對象,或者說數(shù)據(jù),大多數(shù)情況下都是數(shù)據(jù)啦——后頭再詳解。
把棧想象成堆砌起來由上到下的盒子。每次我們調(diào)用一個(gè)方法,就新加一個(gè)盒子到棧頂,我們用這種方法跟蹤我們的程序在執(zhí)行些什么。我們能用的,永遠(yuǎn)只是最頂上的那個(gè)盒子。當(dāng)我們把最頂上這個(gè)盒子用掉了的時(shí)候,也就是方法執(zhí)行完畢并返回的時(shí)候,我們就惡狠狠的把它扔掉!然后接著處理下一個(gè)盒子里的東東。而堆其實(shí)也是類似的東東,除了它的目的是用來保存信息(絕大多數(shù)情況下不是用來跟蹤程序執(zhí)行),因此堆里面的任何東西都不受限制的隨便訪問。堆就像是床上我們沒空收拾的洗好的衣服一般,我們能很快的隨便拿任何一件起來。而棧則跟壁櫥里一堆裝鞋子的盒子似的,我們得一個(gè)一個(gè)的從頂上取下來才能拿到下一雙鞋子。
上圖雖然并不是真正的內(nèi)存中堆棧的樣子,不過有助于我們理解它們的區(qū)別。
棧是“自我維護(hù)”的,意思是基本上是管理自個(gè)兒的(而不是別的地方的)內(nèi)存。當(dāng)頂部盒子不在使用,就扔之(就不是自家的雪了)!而堆呢,不太一樣的是,必須得跟GC打交道——這東西用來保證堆是clean(沒有過多垃圾內(nèi)存)的。(木有人喜歡地上擺一堆臟衣服吧!臭死了!)。
What goes on the Stack and Heap?
當(dāng)代碼執(zhí)行時(shí),堆棧里頭主要放置四種類型的東東:值類型,引用類型,指針(Pointers),以及指令(Instructions)。
值類型:
c#中,值類型繼承自System.ValueType:
bool, byte, char, decimal, double, enum, float, int, long, sbyte, short, struct, uint, ulong, ushort
引用類型:
而引用類型則有:
class, interface, delegate, object, string
指針:
內(nèi)存管理模型中的第三種東東是對一個(gè)類型的引用。這個(gè)引用通常就是指指針。我們不能直接使用指針,它們被CLR所管理。指針不同于引用類型。當(dāng)我們說某某是引用類型時(shí),實(shí)際上就意味著我們要通過指針去訪問這個(gè)類型的值。一個(gè)指針占用內(nèi)存中的一塊空間,只想內(nèi)存中另外一塊空間。指針跟任何別的放在堆棧中的東西一樣,是要占用物理空間的。它的值要么是null,要么就是內(nèi)存地址。
指令:
在后續(xù)文章中再解釋,稍安勿躁……
How is it decided what goes where? (Huh?)
okok,再啰嗦兩句我們就可以正式擺弄我們的堆棧了。
這里有兩條黃金規(guī)則:
棧,就像我們剛才提到的,負(fù)責(zé)跟蹤單個(gè)線程(thread)運(yùn)行到哪兒了。你不妨把其想象成thread的狀態(tài)機(jī),每個(gè)線程都有自個(gè)兒的棧。當(dāng)我們的代碼調(diào)某個(gè)方法時(shí),線程開始執(zhí)行JIT編譯過并且保存在方法表(method table)中的指令集,同時(shí),它把方法參數(shù)壓入線程棧中。然后開始執(zhí)行代碼并訪問方法里需要的、同時(shí)已經(jīng)存在于線程棧頂部的變量。舉個(gè)例子吧:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }讓我們看看棧里頭都發(fā)生了什么,記住我們看到的只是棧頂?shù)臇|西,下頭早有無數(shù)別的東東在里面了哦!
當(dāng)我們開始執(zhí)行這個(gè)方法時(shí),方法的參數(shù)被壓棧(稍后我們討論參數(shù)傳遞)。
Notice: 方法并不存在stack里頭,圖例只是為了演示概念
下一步,控制(線程執(zhí)行這個(gè)方法)被交給AddFive方法在方法表中的指令集,如果這是第一次使用這個(gè)方法,JIT編譯將被執(zhí)行。
在方法的執(zhí)行過程中,我們需要內(nèi)存來保存 "result”,因此棧頂為其分配空間。
方法結(jié)束了,result被返回。
這時(shí)棧頂指針將會(huì)移到最初AddFive方法開始的內(nèi)存地址,這樣所有剛才分配的內(nèi)存空間都被清理掉了,然后接著執(zhí)行AddFive更下面的函數(shù)(圖中為顯示)。
在這個(gè)例子里,result變量被壓棧。事實(shí)上,任何時(shí)候值變量在方法內(nèi)部被聲明,都會(huì)被壓棧。
不過,值變量有時(shí)候也會(huì)存儲(chǔ)在堆里頭。看看黃金規(guī)則二,值類型總是保存在被聲明的地方。那么,如果值類型在方法(method)外部聲明,同時(shí)本身又存在于引用類型內(nèi)部時(shí),那么就會(huì)被保存在堆里頭。
在來一個(gè)例子:
如果我們有MyInt Class(引用類型):
public class MyInt { public int MyValue; }另外一個(gè)方法正在執(zhí)行中:
public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; }跟前頭一樣,線程開始執(zhí)行方法,參數(shù)被壓棧:
這個(gè)時(shí)候開始好玩了:
因?yàn)镸yInt是引用類型,因此保存在堆里頭,通過棧里的指針去引用它。
AddFive執(zhí)行完畢以后,我們開始清理?xiàng)m?#xff1a;
這樣我們就把MyInt對象當(dāng)作孤兒留在了堆里頭,因?yàn)闂V袥]有任何指針在引用它了!
這時(shí)候就是GC大顯身手的時(shí)候啦!一旦我們程序達(dá)到某個(gè)特定的內(nèi)存閥值(threshold)而我們需要更多堆空間時(shí),GC就會(huì)開動(dòng)。GC完全停止所有運(yùn)行中的線程,找到所有堆里沒有被引用的對象,然后像秋風(fēng)掃落葉般的刪除它們。GC重新組織所有滯留在內(nèi)存中活動(dòng)的對象,并調(diào)整堆棧中對其引用的地址。可以看到,從性能角度來說,這可真是很費(fèi)時(shí)間。所以現(xiàn)在你是不是覺得注意一下堆棧的處理會(huì)有助于你寫出高性能的代碼呢?
OK,好了好了,很棒很棒,不過,這東西到底會(huì)如何來折磨俺們呢?
好問題。
當(dāng)我們使用引用類型時(shí),我們實(shí)際上是使用指向該對象的指針,而不是對象本身。當(dāng)我們使用值類型時(shí),我們使用的就是值本身。清楚還是不清楚?
最好還是來個(gè)例子吧。
如果我們執(zhí)行以下方法:
public int ReturnValue() { int x = new int(); x = 3; int y = new int(); y = x; y = 4; return x; }我們得到3,夠簡單吧。
如果這樣呢:
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。
為什么?
在第一個(gè)例子里所有事都按照預(yù)定計(jì)劃行事:
public int ReturnValue() { int x = 3; int y = x; y = 4; return x; }在后面的例子里,我們得不到3是因?yàn)閤和y都指向堆里的同一個(gè)對象。
public int ReturnValue2() { MyInt x; x.MyValue = 3; MyInt y; y = x; y.MyValue = 4; return x.MyValue; }希望本文能幫助您更好的理解值類型和引用類型基本的區(qū)別,以及指針是什么,什么時(shí)候會(huì)用到指針。在后面的系列中,我們進(jìn)一步的闡述內(nèi)存管理,特別的會(huì)多聊聊方法參數(shù)的問題。
轉(zhuǎn)載于:https://www.cnblogs.com/tongdengquan/archive/2010/01/31/6090607.html
總結(jié)
以上是生活随笔為你收集整理的值类型与引用类型比较与区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Silverlight]奇技银巧系列-
- 下一篇: 《 图解 HTTP 》读书笔记