面試必備:C#中堆和棧的區(qū)別和分析
????棧:由編譯器自動分配、釋放。在函數(shù)體中定義的變量通常在棧上。堆:一般由程序員分配釋放。用 new、malloc等分配內(nèi)存函數(shù)分配得到的就是在堆上。存放在棧中時要管存儲順序,保持著先進后出的原則,他是一片連續(xù)的內(nèi)存域,有系統(tǒng)自動分配和維 護;
????堆:是無序的,他是一片不連續(xù)的內(nèi)存域,有用戶自己來控制和釋放,如果用戶自己不釋放的話,當內(nèi) 存達到一定的特定值時,通過垃圾回收器(GC)來回收。棧內(nèi)存無需我們管理,也不受GC管理。當棧頂元素使用完畢,立馬釋放。而堆則需要GC清理。使用引用類型的時候,一般是對指針進行的操作而非引用類型對象本身。但是值類型則操作其本身
詳解:
線程堆棧:簡稱棧 Stack
托管堆:簡稱堆 Heap
使用.Net框架開發(fā)程序的時候,我們無需關心內(nèi)存分配問題,因為有GC這個大管家給我們料理一切。如果我們寫出如下兩段代碼:

1 代碼段1:
2
3 public int AddFive(int pValue)
4 {
5 int result;
6 result = pValue + 5;
7 return result;
8 }


1 代碼段2:
2
3 public class MyInt
4 {
5 public int MyValue;
6 }
7
8 public MyInt AddFive(int pValue)
9 {
10 MyInt result = new MyInt();
11 result.MyValue = pValue + 5;
12 return result;
13 }

問題1:你知道代碼段1在執(zhí)行的時候,pValue和result在內(nèi)存中是如何存放,生命周期又如何?代碼段2呢?
要想釋疑以上問題,我們就應該對.Net下的棧(Stack)和托管堆(Heap)(簡稱堆)有個清楚認識,本立而道生。如果你想提高程序性能,理解棧和堆,必須的!
本文就從棧和堆,類型變量展開,對我們寫的程序進行庖丁解牛。
C#程序在CLR上運行的時候,內(nèi)存從邏輯上劃分兩大塊:棧,堆。這倆基本元素組成我們C#程序的運行環(huán)境。
一,棧 vs 堆:區(qū)別?
棧通常保存著我們代碼執(zhí)行的步驟,如在代碼段1中 AddFive()方法,int pValue變量,int result變量等等。而堆上存放的則多是對象,數(shù)據(jù)等。(譯者注:忽略編譯器優(yōu)化)我們可以把棧想象成一個接著一個疊放在一起的盒子。當我們使用的時候,每次從最頂部取走一個盒子。棧也是如此,當一個方法(或類型)被調用完成的時候,就從棧頂取走(called a Frame,譯注:調用幀),接著下一個。堆則不然,像是一個倉庫,儲存著我們使用的各種對象等信息,跟棧不同的是他們被調用完畢不會立即被清理掉
棧內(nèi)存無需我們管理,也不受GC管理。當棧頂元素使用完畢,立馬釋放。而堆則需要GC(Garbage collection:垃圾收集器)清理。
二,什么元素被分配到棧?什么被分配到堆?
當我們程序執(zhí)行的時候,在棧和堆中分配有四種主要的類型:值類型,引用類型,指針,指令。
值類型:
在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
指針:
在內(nèi)存區(qū)中,指向一個類型的引用,通常被稱為“指針”,它是受CLR( Common Language Runtime:公共語言運行時)管理,我們不能顯示使用。需要注意的是,一個類型的引用即指針跟引用類型是兩個完全不同的概念。指針在內(nèi)存中占一塊內(nèi)存區(qū),它本身只代表一個內(nèi)存地址(或者null),它所指向的另一塊內(nèi)存區(qū)才是我們真正的數(shù)據(jù)或者類型。
指令:
后文對指令再做介紹。
三,如何分配?
我們先看一下兩個觀點:
觀點1,引用類型總是被分配在堆上。(正確?)
觀點2,值類型和指針總是分配在被定義的地方,他們不一定被分配到棧上。(這個理解起來有點難度,需要慢慢來)
上文提及的棧(Stack),在程序運行的時候,每個線程(Thread)都會維護一個自己的專屬線程堆棧。
當一個方法被調用的時候,主線程開始在所屬程序集的元數(shù)據(jù)中,查找被調用方法,然后通過JIT即時編譯并把結果(一般是本地CPU指令)放在棧頂。CPU通過總線從棧頂取指令,驅動程序以執(zhí)行下去。
下面我們以實例來詳談。
還是我們開篇所列的代碼段1:

1 public int AddFive(int pValue)
2 {
3 int result;
4 result = pValue + 5;
5 return result;
6 }

當AddFive方法開始執(zhí)行的時候,方法參數(shù)(parameters)則在棧上分配。
接著,指令指向AddFive方法內(nèi)部,如果該方法是第一次執(zhí)行,首先要進行JIT即時編譯。
當方法內(nèi)部開始執(zhí)行的時候,變量result被分配在棧上,方法執(zhí)行完畢,而且方法返回后,棧上的區(qū)域被清理。
以上看出,一個值類型變量,一般會分配在棧上。那觀點2中所述又做何理解?“值類型和指針總是分配在被定義的地方,他們不一定被分配到棧上”。
原因就是如果一個值類型被聲明在一個方法體外并且在一個引用類型中,那它就會在堆上進行分配。
還是代碼段2:

1 public class MyInt
2 {
3 public int MyValue;
4 }
5
6 public MyInt AddFive(int pValue)
7 {
8 MyInt result = new MyInt();
9 result.MyValue = pValue + 5;
10 return result;
11 }

當線程開始執(zhí)行AddFive方法的時候,參數(shù)被分配到棧上,由于MyInt是一個引用類型,所以它被分配到堆上,并且在棧中生成一個指針(result),AddFive方法執(zhí)行完畢時,棧上內(nèi)存被清理,堆中依然存在。
????當程序需要更多的堆空間時,GC需要進行垃圾清理工作,暫停所有線程,找出所有不可達到對象,即無被引用的對象,進行清理。并通知棧中的指針重新指向地址排序后的對象。現(xiàn)在我們應該知道,了解棧和堆,對我們開發(fā)出高性能程序的重要性。當我們使用引用類型的時候,一般是對指針進行的操作而非引用類型對象本身。但是值類型則操作其本身。
接下來,我們用例子說明這一點。

1 例1:
2
3 public int ReturnValue()
4 {
5 int x = new int();
6 x = 3;
7 int y = new int();
8 y = x;
9 y = 4;
10 return x;
11 }

執(zhí)行結果為3,稍作修改:

1 例2:
2
3 public class MyInt
4 {
5 public int MyValue;
6 }
7
8 public int ReturnValue2()
9 {
10 MyInt x = new MyInt();
11 x.MyValue = 3;
12 MyInt y = new MyInt();
13 y = x;
14 y.MyValue = 4;
15 return x.MyValue;
16 }

執(zhí)行結果為4。
我們來分析下原因,其實例1的跟以下代碼所起效用一樣:

1 public int ReturnValue()
2 {
3 int x = 3;
4 int y = x;
5 y = 4;
6 return x;
7 }

在棧上x和y分別占用一塊內(nèi)存區(qū),互不干擾。
而例2,與以下代碼所起效用一樣:

1 public int ReturnValue2()
2 {
3 MyInt x;
4 x.MyValue = 3;
5 MyInt y;
6 y = x;
7 y.MyValue = 4;
8 return x.MyValue;
9 }

引用:https://www.cnblogs.com/yplong/p/3467025.html
版權申明:本文來源于網(wǎng)友收集或網(wǎng)友提供,如果有侵權,請轉告版主或者留言,本公眾號立即刪除。
支持小微:
騰訊云 搞活動了?玩服務器的可以搞搞。就這幾天時間。
輕量?1C2G 50GB SSD盤 255元/3年
鏈接:https://curl.qcloud.com/qINmPBX9
右下角,您點一下在看圖片
小微工資漲1毛
商務合作QQ:185601686
收錄于話題?#C#知識學習
10個
下一篇C#怎么獲取兩個數(shù)組或集合的交集或差集?
