接觸NET也有年左右的時間了NET的內部實現對我產生了很大的吸引力個人覺得能對這些底部的實現進行了解和熟練的話對以後自己寫代碼是有很大幫助的好了廢話不多說請看下邊
NET CLR 和 Java VM 都是堆疊式虛擬機器(StackBased VM)也就是說它們的指令集(Instruction Set)都是採用堆疊運算的方式執行時的資料都是先放在堆疊中再進行運算JavaVM 有約 個指令(Instruction)每個指令都是 byte 的 opcode(操作碼)後面接不等數目的參數NET CLR 有超過 個指令但是有些指令使用相同的 opcode所以 opcode 的數目比指令數略少特別注意NET 的 opcode 長度並不固定大部分的 opcode 長度是 byte少部分是 byte
下面是一個簡單的 C# 原始碼
復制代碼 代碼如下:
using System;
public class Test {
public static void Main(String[] args) {
int i=
;
int j=
;
int k=
;
int answer = i+j+k;
Console
WriteLine("i+j+k="+answer);
}
}
將 此原始碼編譯之後可以得到一個 EXE的程序我們可以通過 ILDASMEXE(圖) 來反編譯 EXE 以觀察IL我將 Main() 的 IL 反編譯條列如下這裡共有十八道IL 指令有的指令(例如 ldstr 與 box)後面需要接參數有的指令(例如 ldci 與與add)後面不需要接參數
圖
ldci
stloc
ldci
stloc
ldci
stloc
ldloc
ldloc
add
ldloc
add
stloc
ldstr "i+j+k="
ldloc
box [mscorlib]SystemInt
call string [mscorlib]SystemString::Concat(object object)
call void [mscorlib]SystemConsole::WriteLine(string)
ret
此程式執行時關鍵的記憶體有三種分別是
Managed Heap這是動態配置(Dynamic Allocation)的記憶體由 Garbage Collector(GC)在執行時自動管理整個Process 共用一個 Managed Heap
Call Stack這是由 NET CLR 在執行時自動管理的記憶體每個 Thread 都有自己專屬的 Call Stack每呼叫一次 method就會使得Call Stack 上多了一個 Record Frame呼叫完畢之後此 Record Frame 會被丟棄一般來說Record Frame 內記錄著 method 參數(Parameter)返回位址(Return Address)以及區域變數(Local Variable)Java VM 和 NET CLR 都是使用 … 編號的方式來識別區別變數
Evaluation Stack這是由 NET CLR 在執行時自動管理的記憶體每個 Thread 都有自己專屬的 Evaluation Stack前面所謂的堆疊式虛擬機器指的就是這個堆疊
後面有一連串的示意圖用來解說在執行時此三種記憶體的變化首先在進入 Main() 之後尚未執行任何指令之前記憶體的狀況如圖 所示
圖
接著要執行第一道指令 ldci此指令的意思是在 Evaluation Stack 置入一個 byte 的常數其值為 執行完此道指令之後記憶體的變化如圖 所示
ldci表示加載一個值為到堆棧中該條指令的語法結構是
ldctypevalueldc指令加載一個指定類型的常量到stack
ldcinumberldc指令更加有效它傳輸一個整型值以及到之間的整數給計算堆棧
圖
接著要執行第二道指令 stloc此指令的意思是從 Evaluation Stack 取出一個值放到第 號變數(V)中這裡的第 號變數其實就是原始碼中的i執行完此道指令之後記憶體的變化如圖 所示
圖
後面的第三道指令和第五道指令雷同於第一道指令且第四道指令和第六道指令雷同於第二道指令為了節省篇幅我不在此一一贅述提醒大家第 號變數(V)其實就是原始碼中的 j且第 號變數(V)其實就是源碼中的 k圖~ 分別是執行完第三~六道指令之後記憶體的變化圖
圖
圖
圖
圖
接著要執行第七道指令 ldloc 以及第八道指令 ldloc分別將 V(也就是 i)和 V(也就是 j)的值放到 Evaluation Stack這是相加前的准備動作圖 與圖 分別是執行完第七第八道指令之後記憶體的變化圖
圖
圖
接著要執行第九道指令 add此指令的意思是從 Evaluation Stack 取出兩個值(也就是 i 和 j)相加之後將結果放回 Evaluation Stack 中執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十道指令 ldloc此指令的意思是分別將 V(也就是 k)的值放到 Evaluation Stack這是相加前的准備動作執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十一道指令 add從 Evaluation Stack 取出兩個值相加之後將結果放回 Evaluation Stack 中此為 i+j+k 的值執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十二道指令 stloc從 Evaluation Stack 取出一個值放到第 號變數(V)中這裡的第號變數其實就是原始碼中的 answer執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十三道指令 ldstr "i+j+k="此指令的意思是將 "i+j+k=" 的 Reference 放進 Evaluation Stack執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十四道指令 ldloc將 V 的值放進 Evaluation Stack執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十五道指令 box [mscorlib]SystemInt從此處可以看出int到string實際是進行了裝箱操作的所以會有性能損失可以在以後的編碼中減少裝箱操作來提高性能此指令的意思是從 Evaluation Stack 中取出一個值將此 Value Type 包裝(box)成為 Reference Type執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十六道指令 call string [mscorlib] SystemString::Concat(object object)此指令的意思是從 Evaluation Stack 中取出兩個值此二值皆為 Reference Type下面的值當作第一個參數上面的值當作第二個參數呼叫 mscorlibdll 所提供的 SystemStringConcat() method 來將此二參數進行字串接合(String Concatenation)將接合出來的新字串放在 Managed Heap將其 Reference 放進 Evaluation Stack值得注意的是由於 SystemStringConcat() 是 static method所以此處使用的指令是 call而非 callvirt(呼叫虛擬)執行完此道指令之後記憶體的變化如圖 所示
圖
請注意此時 Managed Heap 中的 Int() 以及 String("i+j+k=") 已經不再被參考到所以變成垃圾等待 GC 的回收
接著要執行第十七道指令 call void [mscorlib] SystemConsole::WriteLine(string)此指令的意思是從 Evaluation Stack 中取出一個值此值為 Reference Type將此值當作參數呼叫 mscorlibdll 所提供的 SystemConsoleWriteLine() method 來將此字串顯示在 Console 視窗上SystemConsoleWriteLine() 也是 static method執行完此道指令之後記憶體的變化如圖 所示
圖
接著要執行第十八道指令 ret此指令的意思是結束此次呼叫(也就是 Main 的呼叫)此時會檢查 Evaluation Stack 內剩下的資料由於 Main() 宣告不需要傳出值(void)所以 Evaluation Stack 內必須是空的本范例符合這樣的情況所以此時可以順利結束此次呼叫而 Main 的呼叫一結束程式也隨之結束執行完此道指令之後(且在程式結束前)記憶體的變化如圖 所示
圖
通過此范例讀者應該可以對於 IL 有最基本的認識對 IL 感興趣的讀者應該自行閱讀 Serge Lidin 所著的《Inside Microsoft NET IL Assembler》(Microsoft Press 出版)我認為熟知 IL 每道指令的作用是 NET 程式員必備的知識NET 程式員可以不會用 IL Assembly 寫程式但是至少要看得懂 ILDASM 反編譯出來的 IL 組合碼
From:http://tw.wingwit.com/Article/program/net/201311/14024.html