以前剛開始學C#的時候,總有高手跟我說,去了解一下IL代碼吧,看懂了你能更加清楚的知道你寫出來的代碼是如何運行互相調用的,可是那時候沒去看,後來補的,其實感覺也不晚。剛開始看IL代碼的時候,感覺非常吃力,一大堆不懂,後來,慢慢看,最後也能看得懂一丁點啦。
閒話不多說了,下面就開始講講IL代碼
1、什麼是IL代碼
IL,也稱為CIL,MSIL,是.NET框架中中間語言(Intermediate Language)的縮寫。上一篇文章已經說過了,Visual Studio繼承的C#編譯器可以直接把C#寫的源程序編譯成.exe或.dll格式的文件,這些文件裡面保存的就是IL代碼,這些代碼CPU是認不得的,只能再經過JIT編譯後,CPU才會執行。
2、How to Study IL
IL的代碼形式比較特殊,看起來會比較吃力,理解全部肯定更困難。但在這個世上有一個定律叫做“二八原則”,20%的人掌握著世上80%的財富。這在編程上也是一樣的,80%的功能其實只需要用20%的技術就可以完成了,但另外的20%,就有可能需要80%的技術了。學習IL代碼也是一樣,它有200多個指令(可以查看這裡:IL指令),我們只要學習常用的20%就可以解決80%的問題了。不管怎麼說,就是要多看,看多了自然就會懂了。
3、怎麼查看源代碼
(1)先寫正常程序,通過編譯
(2)找Bin文件夾中找到exe後綴的文件
(3)拖入Reflector(我習慣用這個來看反編譯代碼),也可以使用別的反編譯軟件,比如ILDasm,ILSpy等。初學者我是建議使用ILDasm,因為這是微軟自帶的。
我在網上找了兩張圖,是使用ILDasm的,大家可以參考借鑒一下。
上面兩張圖是用ILDasm的。而我還是習慣用Reflector。
上圖的右邊就是傳說中的IL代碼了,看起來復雜嗎?應該不復雜吧,來,再多看幾眼......下面我就一句一句來解釋。
//Call Stack是調用棧,一個局部變量列表,用於存儲.locals init([0] int32 num,[1] int32 num2,[2] int32 num3)初始化變量。
//Evaluation Stack也是一個評估棧,用來存儲值,比如ldc.i4.1這種指令會把1壓入棧中等待操作。
//棧是一種先進後出的數據結構。
//hidebysig指令表示如果當前類為父類,用該指令標記的方法將不會被子類繼承通過上面的代碼,我們可以總結一下: .maxstack:代碼中變量需要在調用棧(Call Stack)中占用幾個位置; .locals int(......):定義變量初始化並放入調用棧中(Call Stack); nop:No Operation,沒有任何操作; ldstr:Load String,把字符串壓入評估棧(Evaluation Stack)中; ldc.i4.1:把數值2以4字節長度整數的形式壓入評估棧; stloc:把評估棧(Evaluation)中的值彈出賦值到調用棧中(Call Stack); ldloc:把調用棧(Call Stack)中指定位置的值取出(Copy)壓入評估棧(Evaluation Stack)中; call:調用指定的方法,這個指令一般用於調用靜態方法;而callvir則一般用於調用實例方法; ret:return ,標記返回。
//cil managed表明方法體中的代碼是IL代碼,且是托管代碼,即運行在CLR運行庫上的代碼
.method private hidebysig static void Main(string[] args)cil managed { .entrypoint //該指令代表該函數程序的入口函數。每一個托管應用程序都有且只有一個入口函數,CLR加載程序時,首先從.entrypoint函數開始執行。 .maxstack 2 //執行構造函數時,評估堆棧可容納數據項的最大個數。評估堆棧是保存方法中所需要變量的值的一個內存區域,該區域在方法執行結束時會被清空,或者存儲一個返回值。 .locals init ( [0] int32 num, [1] int32 num2, [2] int32 num3) //表示定義int類型的變量,變量名分別為num,num2,num3。存儲在調用棧。 L_0000: nop //No operation的意思,即沒有任何操作。 L_0001: ldc.i4.1 //將“1”壓入評估棧,此時“1”處於評估棧的棧頂。 L_0002: stloc.0 //此指令表示把值從評估棧中彈出,並賦值給調用棧的第0個變量num。 L_0003: ldc.i4.2 L_0004: stloc.1 L_0005: ldc.i4.3 L_0006: stloc.2 //從.locals init到L_0006,相當於C#代碼的為i,j,k賦值。 L_0007: ldloc.0 //取調用棧中位置為0的元素壓入評估棧(取i的值)。 L_0008: ldloc.1 //取調用棧中位置為1的元素壓入評估棧(取j的值)。 L_0009: add //做加法操作 L_000a: ldloc.2 //取調用棧中位置為2的元素壓入評估棧(取k的值)。 L_000b: add //做加法操作 L_000c: call void [mscorlib]System.Console::WriteLine(int32) //調用輸出方法 L_0011: nop //No Operation L_0012: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() //調用ReadKey方法 L_0017: pop //把評估棧的內容清空 L_0018: ret //return 標記返回值 } //Main方法結束
以上的IL代碼算是比較簡單的一段代碼,因為沒有條件判斷等流程控制。但只要記住每一條IL指令固定的操作,我覺得也不難。接下來會寫第二部分,深入理解IL代碼。