国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

.NET內存基礎(通過內存體驗類型、傳參、及裝箱拆箱)

2019-11-14 16:28:38
字體:
來源:轉載
供稿:網友

該隨筆受啟發于《CLR Via C#(第三版)》第四章4.4運行時的相互聯系

一、內存分配的幾個區域

1、線程棧

局部變量的值類型  局部變量中引用類型的指針(或稱引用)會被分配到該區域上(引用類型的一部分內存被分配到該區域內)。

該區域由系統管控,不受垃圾收集器的控制。當所在方法執行完畢后,局部變量會自動釋放(引用類型只釋放指針,而不釋放指針指向的數據)

堆棧的執行效率很高,但容量有限。

2GC Heap(回收堆)

用于分配小對象(引用類型),如果引用類型的實例大小 小于85000個字節,則會被分配到該區域上。

3LOH(Large Object Heap)

超過 85000個字節的大對象(引用類型)會被分配到該區域上。

LOH GC Heap區別在于:當有內存分配或者回收時,垃圾收集器可能會對GC Heap進行壓縮,而 LOH 不會被壓縮,只是在垃圾回收時被回收。

 

 

二、棧楨(stack frame)

棧楨是實現函數調用的數據結構,從邏輯上看,棧楨是一個函數執行的環境(上下文),包括:函數參數、返回地址和函數局部變量。

注意:上述的“返回地址” 不是函數的返回值,而是完成函數調用后的CPU接下來要執行的代碼的位置。

更多參見: 維基百科 call stack 

 

三、淺拷貝 和 深拷貝(Clone、或克隆)

淺拷貝和深拷貝是內存拷貝的兩種方式,在傳參的過程中通常會發生內存拷貝并且是淺拷貝,比如下面代碼、兩個參數就是兩次淺拷貝。

        public void Test1()        {            string name = "test1";            int size = 1;            Test2(name, size);// 兩次淺拷貝        }        public void Test2(string pName, int pSize)        {        }

 淺拷貝 前后的內存結果如下圖所示:

淺拷貝對于值類型(例如:int),拷貝的是棧中的具體值。

淺拷貝對于引用類型(例如:string),拷貝的是棧中的引用、其新引用 所指向的 托管堆中的地址 還是原來的地址。

 

深拷貝和淺拷貝的區別在于對 堆中數據(即對象)的處理

淺拷貝只拷貝棧中的引用,不拷貝堆中的對象,新引用指向原有對象。

深拷貝會拷貝棧中的引用,并且會在堆中創建新的對象,新對象的屬性值 “可能” 來源于原有對象(因為、在C#中實現深拷貝需要自己編寫額外的代碼,

即實現 ICloneable 接口,深拷貝的結果是不確定的。不過建議大家盡可能避免使用該接口),新引用地址指向新的對象。因此,深拷貝只針對于引用類型,

對于值類型沒有太多的意義。

 

四、Demo

如果說前三項是做鋪墊,那么該Demo算是正文了。

事先已經定義好的如下類:

    public class Data     {        public string Name;        public int Size;    }

假設代碼即將調用Test1函數:

        public void Test1()        {            string name = "test1";            int size = 1;            Data data = new Data() { Name = "test1", Size = 1 };            Test2(name, size, data);            object obj = size;            int temp = (int)obj;        }        public void Test2(string pName, int pSize, Data pData)        {            pName = "test2";            pSize = 2;            pData.Name = "test2";            pData.Size = 2;                        pData = new Data() { Name = "test3", Size = 3 };                    }

此時線程棧和托管堆內的情況如下圖所示(圖1):

 

1、開始調用Test1函數,這時會向棧中壓入一個棧楨(stack frame)。

  棧楨包含3部分數據:

  1)函數參數,當然Test1函數沒有參數。

  2)返回地址,在Test1函數中,以當前代碼環境為例該地址沒有什么實際意義不作說明。

  3)函數局部變量,此時Test1函數中的所有的局部變量都會被壓入棧。 有如下五個局部變量會被壓入棧:

string name, int32 size, Data data, object obj, int32 temp。(當然實際是六個局部變量,第六個是編譯器自動生成的,在隨筆結尾處再做解釋。)

 壓入一個棧楨后,內存結果如下圖所示(圖2):

 

2、接下來執行Test1函數的代碼,給name賦值,給size賦值,給data賦值。內存結果將會如下圖所示(圖3):

上圖中的紅線編譯器生成的變量 將在隨筆結尾出做解釋。

 

3、接下來將調用Test2函數,這時還會向棧中壓入一個棧楨。

棧楨包含3部分數據:

  1)函數參數,Test2函數有三個參數,三個參數都是淺拷貝。

  2)返回地址,該地址為調用Test2函數代碼位置的下一行(或者稱下一個指令)的位置,即:

obj = size; // 在調用Test1函數壓入棧楨的時候已經完成了 object obj; 的操作

  完成函數調用后,將執行上述代碼。

  3)函數局部變量,編譯器會自動生成一個變量(隨筆結尾處,將做出解釋)

故,此時的內存結果如下圖所示(圖4):

兩條橙色線是參數淺拷貝的結果。

 

4、 在執行完Test2函數的函數體,未返回Test1函數之前,內存結果如下圖所示(圖5):

紅線 將在隨筆結尾處做解釋。

黃線 需要注意,string是特殊的引用類型,其外在表現和值類型一致,但其本質上還是引用類型。由于string的不可變性,對string的每一次賦值

  都會產生一個新的對象(如果新對象不存在),所以導致了黃線的出現。

淺綠線 我想這條線應該沒有問題吧。

 

5、 完成Test2函數的調用,代碼將返回到 Test2函數棧楨返回地址所示位置,在這一過程中 Test2函數棧楨 將被彈出,結果如下圖所示(圖6):

托管堆中的不被使用的對象將由GC進行自動回收,被回收的時間不確定。

 

6、 由3可以知,接下來將執行 obj = size; 的操作——裝箱,從字面上可以這樣理解:將值類型裝進引用類型的箱子里。

大致的操作過程如下:

  1)首先、在堆中創建一個新的對象。(由于該操作導致裝箱操作的性能降低)

  2)將size的值復制到對象中。

  3)最后將obj的引用指向新創建的對象。

最后的結果如下圖所示(圖7):

 

7、最后一個操作——拆箱

裝箱,是將值類型裝進引用類型的箱子里,在這一過程中,發生對象創建和內存復制。 

然而拆箱,并沒有那么復雜,也沒有類似的“拆”的過程,只是將對象中的值類型讀取出來(最耗時的操作應該是尋找值類型所在的內存地址的操作),相對于裝箱,其性能要好很多。

最終結果如下圖所示(圖8):

 

 

五、 語法糖——對象初始化器

如下的兩個代碼片段是完全等效的:

            Data temp = new Data(); // 編譯器自動生成的變量名不一定叫temp            temp.Name = "test1";            temp.Size = 1;                        data = temp;
       data = new Data() { Name = "test1", Size = 1 }; 

第二種寫法,可以看作是一種簡寫,編譯器在編譯的時候會將第二種寫法的代碼進行轉化,轉化成第一種寫法。所以對象初始化器,C#3.0的語法新特性,

完全是一種語法糖,CLR沒有起到任何作用。

當看到這的時候,再想想上面的紅線 一切迎刃而解。

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 定襄县| 茌平县| 柯坪县| 田东县| 广河县| 安岳县| 八宿县| 花莲市| 扬中市| 佳木斯市| 磐石市| 信丰县| 荆州市| 赣州市| 仁怀市| 武山县| 连州市| 曲靖市| 靖安县| 棋牌| 茌平县| 曲沃县| 惠来县| 马关县| 灵寿县| 汝城县| 油尖旺区| 铜梁县| 监利县| 东台市| 静安区| 永宁县| 彩票| 乌恰县| 崇阳县| 吉林市| 西乌珠穆沁旗| 寿宁县| 仁寿县| 温宿县| 富民县|