1. 程式人生 > >CLR via C#學習筆記-第四章-類型基礎-運行時的相互關系

CLR via C#學習筆記-第四章-類型基礎-運行時的相互關系

分配內存 ring type類 實現 語句 初始化 sem strong 允許

4.4 運行時的相互關系

已加載CLR的一個Windows進程,該進程可能有多個線程。線程創建時會分到1MB的棧。棧空間用於向方法傳遞實參,方法內部定義的局部變量也在棧上。

以下是方法M1和M2的偽代碼

void M1()
{
    String name="Joe";
    M2(name);
    ...
    return;
}
void M2(String s)
{
    Int32 length=s.Length;
    Int32 tally;
    ...
    return;
}

現在,假定線程執行的代碼要調用M1方法。最簡單的方法包含序幕(prologue)代碼,在方法開始做工作前對其進行初始化。還包含尾聲(epilogue)代碼,在方法做完工作後對其進行清理,以便返回至調用者。M1方法開始執行時,他的序幕代碼在線程棧上分配局部變量name的內存,如下圖1所示。

技術分享圖片

圖1

然後M1調用M2方法,將局部變量name作為實參傳遞。這造成name局部變量中的地址被壓入棧。M2方法內部使用參數s標識棧位置。另外,調用方法時還會將返回地址壓入棧。被調用的方法在結束後應返回至該位置。如下圖2所示。

技術分享圖片

圖2

M2方法開始執行,他的序幕代碼在線程棧中為局部變量length和tally分配內存,如下圖3所示。然後M2方法內部的代碼開始執行,最終M2抵達他的return語句,造成CPU的指令指針被設置為棧中的返回地址,M2的棧幀展開(unwind),恢復成圖1的樣子.之後,M1繼續執行M2調用之後的代碼。M1的棧幀將準確反映M1需要的狀態。最終,M1會返回到他的調用者,圖中未顯示,應在name實參上方。

技術分享圖片

圖3

假定有以下兩個類定義

internal class Employee
{
    public Int32 GetYearsEmployed(){}
    public virtual String GetProgressReport(){}
    public static Employee Lookup(String name){}
}
internal sealed class Manager:Employee
{
    public override String GetProgressReport(){}
}

WIndows進程已啟動,CLR已加載到其中,托管堆已初始化,而且已創建一個線程(連同他的1MB棧空間)。線程已執行了一些代碼,馬上就要調用M3方法,代碼如下所示。

void M3()
{
    
    e=new Manager();
    e=Employee.Lookup("Joe");
    year=e.GetYearsEmployed();
    e.GetProgressReport();
}

堆上所有對象都包含兩個額外成員:類型對象指針和同步塊索引。如圖4所示,Employee和Manager類型對象都有這兩個成員。定義類型時,可在類型內部靜態數據字段。為這些靜態數據字段提供支援的字節在類型對象自身中分配。每個類型對象最後都包含一個方法表。在方法表中,類型定義的每個方法都有對應的記錄項。

當CLR確認方法需要的所有類型對象都以創建,M3的代碼已經編譯之後,就允許線程執行M3的本機代碼。M3的序幕代碼執行時必須在線程中為局部變量分配類型,如下圖4所示。作為方法序幕代碼的一部分,CLR自動將所有局部變量初始化為null或0,。然而如果代碼試圖訪問尚未顯式初始化的局部變量,C#匯報稿錯誤消息:使用了未賦值的局部變量。

技術分享圖片

圖4

然後M3執行代碼構造一個Manager對象,這造成在托管堆創建Manager類型的一個勢力,如下圖5所示.可以看出,和所有對象一樣,Manager對象也有類型對象指針和同步塊索引。該對象還包含必要的字節類容納Manager類型定義的所有實例數據字段,以及容納由Manager的任何基類(本例就是Employee和Object)定義的所有實例字段。

任何時候在堆上新建對象,CLR都自動初始化內部的類型對象指針成員來引用和對象對應的類型對象。此外,在調用類型的構造器(本質可能是修改某些實例數據字段的方法)之前,CLR會先初始化同步塊索引,並將對象的所有實例字段設為null或0.new操作符返回Manager對象的內存地址,該地址保存到變量e中(e在線程棧上)。

技術分享圖片

圖5

M3的下一行代碼調用Employee的靜態方法Lookup。調用靜態方法時,CLR會定位與靜態方法的類型對應的類型對象。然後,JIT編譯器在類型對象的方法表中查找與被調用方法對應的記錄項,對方法進行JIT編譯(如果需要的話)。再調用JIT編譯好的代碼。

本例假定Employee的Lookup方法要查詢數據庫來查找Joe,再假定數據庫支出Joe是公司的一名經理,所以在內部,Loouo方法在堆上構造出一個新的Manager對象,用Joe的信息初始化它,返回該對象的地址。該地址保存到局部變量e中,這個操作如下圖6所示。

註意,e不再引用第一個Manager對象。事實上,由於沒有變量引用該對象,所以他是未來垃圾回收的主要目標。垃圾回收機制將自動回收(釋放)該對象占用的內存。

技術分享圖片

圖6

M3的下一行代碼調用Employee的非虛實例方法GetYearsEmployed。調用非虛實例方法時,JIT編譯器會找到與“發出調用的哪個變量e的類型Employee”對應的類型對象即Employee類型對象。這是的變量額被定義成一個Employee,如果Employee類型沒有定義正在調用的那個方法,JIT編譯器會回溯類層次結構(一直回溯到Object),並在沿途的每個類型中查找該方法。之所以能這樣回溯,是因為每個類型對象都有一個字段引用了它的基類型,這個信息在圖中沒有顯示。

然後,JIT編譯器在類型對象分方法表中查找引用了被調用方法的記錄項,對方法進行JIT編譯(如果需要的話),再調用JIT編譯好的代碼。本例假定Employee的GetYearsEmployed方法返回5,這個操作如下圖7所示。

技術分享圖片

圖7

M3的下一行代碼調用Employee的虛實例方法GetProgressReport。調用虛實例方法時,JIT編譯器要在方法中生成一些額外代碼。方法每次調用都會執行這些代碼。這些代碼首先檢查發出調用的變量並跟隨地址來到發出調用的對象。變量e當前引用的是代表"Joe"的Manager對象。然後代碼在類型對象的方法表中查找引用了被調用方法的記錄項,對方法進行JIT編譯(如果需要的話),再調用JIT編譯好的代碼。由於e引用一個Manager對象,所以會調用Manager的GetProgressReport實現。這個操作如下圖8所示。

技術分享圖片

圖8

註意,如果Employee的Lookup方法發現Joe是Employee而不是Manager,Lookuo會在內部構造一個Employee對象,他的類型對象指針將引用Employee類型對象。這樣最終執行但就是Employee的GetProgressReport實現,而不是Manager的。

註意Employee和Manager類型對象都包含類型對象指針。這是由於類型對象本質上也是對象。CLR創建對象時,必須初始化這些成員。CLR開始在一個進程運行時,會立即為MSCorLib.dll中定義的System.Type類型創建一個特殊的類型對象。Employment和Manager類型對象都是該類型的“實例”。因此,他們的類型對象指針成員會初始化成對System.Type類型對象的引用。如下圖9所示。

當然,System.Type類型對象本身也是對象,內部也有類型對象指針成員,這個指針指向它本身。因為System.Type類型對象本身是一個類型對象的“實例”。

System.Object的GetType方法返回存儲在指定對象的類型對象指針成員中的地址。也就是說GetType方法返回指向對象的類型對象的指針。這樣就可判斷系統中任何對象的真實類型。

技術分享圖片

圖9

CLR via C#學習筆記-第四章-類型基礎-運行時的相互關系