讀懂IL程式碼就這麼簡單 (一)
轉:https://www.cnblogs.com/zery/p/3366175.html
讀懂IL程式碼就這麼簡單 (一)
一前言
感謝 @冰麟輕武 指出文章的錯誤之處,現已更正
對於IL程式碼沒了解之前總感覺很神奇,初一看完全不知所云,只聽高手們說,瞭解IL程式碼你能更加清楚的知道你的程式碼是如何執行相互呼叫的,此言一出不明覺厲。
然後開始接觸IL,瞭解了一段時後才發現原來讀懂IL程式碼並不難。進入正題
1.1 什麼是IL
IL是.NET框架中中間語言(Intermediate Language)
1.2 為什麼要了解IL
在很多時候不明白程式碼是如何操作時就可以通過IL指令來解釋,比如,裝箱,拆箱是否只是聽別人說或者書上講是怎麼怎麼實現的,自己是否證實過呢?瞭解IL指令你可清楚看到是每一步是如何處理的
1.3 怎麼學IL
世上有個定律叫“二八定律” ,80%的功能,只要用20%的技術就可以完成,但要完成另外20%可能就需要80%技術了,對於IL程式碼也是如此,有200多個指令,我們只需要用到其20%的指令就可以解決我們80%的問題了,所以我不會寫太多,只是讓大家能看懂普通的程式程式碼編譯成IL程式碼後就行了,還有就是要多看,IL程式碼的每一條指令都是特定的意思,看得多了自然就懂了,當對自己程式碼有疑問時嘗試看看它對應的IL程式碼,也許你會了解得更多。
IL指令大全 點這裡
IL程式碼編譯器 ILDasm 點這裡
二 如何檢視IL程式碼
2.1 步驟
1 編寫程式碼並編譯通過
2 找到原始檔的obj檔案下的 .exe檔案
3 匯入到ILDasm中反編譯成IL程式碼
上圖
1 -2步 3 匯入到ILDasm中
ILDasm中圖示含義
三 如何讀IL(大致瞭解)
以上步驟完成後我們就可以看到程式碼被編譯後的IL程式碼,以下部份將會對每一條IL指令做詳細的解釋
C#程式碼
1 static void Main(string[] args) 2 { 3 int i = 1; 4 int j = 2; 5 int k = 3; 6 Console.WriteLine(i+j+k); 7 }
IL程式碼
// Call Stack是一個棧,而Call Stack中的Record Frame則是一個區域性變數列表,用於儲存 .locals init (int32 V_0,int32 V_1,int32 V_2)初始化後的引數 V_0,V_1,V_2
因圖中沒有把Record Frame 標記出來,所以自己畫了一張圖
// Evaluation Stack 是一個棧 ldc.i4.2 這種指令都會先把值壓入棧中等待操作
在第四段時大家可以理解得更清楚一點
另外@Learning hard 指出IL指令中第 9 11 13行容易讓人誤解值是從Record Frame中載入的
現強調IL指令中 第 9 11 13行的ldc.i4.1,ldc.i4.2,ldc.i4.3 執行這幾條指令時 值是還沒有載入到Record Frame中的,但是MSDN也沒有指出從哪裡載入
所以只能根據個人的想法解釋,程式在編譯後值型別資料會存線上程棧中,所以我認為此時的9 11 13行的值是從執行緒棧中取的
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint //程式入口 4 // Code size 19 (0x13) 5 .maxstack 3 //定義函式程式碼所用堆疊的最大深度,也指Evaluation Stackk中最多能同時存在3個值 6 //以下我們把它看做是完成程式碼中的初始化 7 .locals init (int32 V_0,int32 V_1,int32 V_2) //定義 int 型別引數 V_0,V_1,V_2 (此時已經把V_0,V_1,V_2存入了Call Stack中的Record Frame中) 8 IL_0000: nop //即No Operation 沒有任何操作,我們也不用管它 9 IL_0001: ldc.i4.1 //載入第一個變數"i"的值 (壓入Evaluation Stack中) 10 IL_0002: stloc.0 //從棧中把"i"的值彈出並賦值給Record Frame中第0個位置(V_0)
11 IL_0003: ldc.i4.2 //載入第二個變數"j"的值 (壓入Evaluation Stack中) 12 IL_0004: stloc.1 //從棧中把"j"的值彈出並賦值給Record Frame中第1個位置(V_1)
13 IL_0005: ldc.i4.3 //載入第三個變數"k"的值 (壓入Evaluation Stack中) 14 IL_0006: stloc.2 //從棧中把 "k"的值彈出並賦值給Record Frame中第2個位置(V_2)
15 16 //上面程式碼初始化完成後要開始輸出了,所以要把資料從Record Frame中取出 17 18 IL_0007: ldloc.0 //取Record Frame中位置為0的元素(V_0)的值("i"的值)並壓入棧中 (相當於Copy一份值Call Stack中V_0的值。V_0本身的值是不變的) 19 IL_0008: ldloc.1 //取Record Frame中位置為1的元素(V_1)的值("j"的值)並壓入棧中 (同上) 20 IL_0009: add // 做加法操作 21 IL_000a: ldloc.2 // 取出Record Frame中位置為2的元素(V_2)的值("k"的值)並壓入棧中 22 IL_000b: add // 做加法操作 23 IL_000c: call void [mscorlib]System.Console::WriteLine(int32) //呼叫輸出方法 24 IL_0011: nop 25 IL_0012: ret //即為 return 標記 返回值 26 } // end of method Program::Main
指令詳解
.maxstack:評估堆疊(Evaluation Stack)可容納資料項的最大個數
.locals init (int32 V_0,int32 V_1,int32 V_2):定義變數並存入Call Stack中的Record Frame中
nop:即No Operation 沒有任何操作,我們也不用管它,
ldstr.:即Load String 把字串加壓入Evaluation Stack中
stloc.:把Evaluation Stack中的值彈出賦值到Call Stack中的Record Frame中
ldloc.:把Call Stack中的Record Frame中指定位置的值取出(copy)存入 Evaluation Stack中 以上兩條指令為相互的操作stloc賦值,ldloc取值
call: 呼叫指定的方法
ret: 即return 標記返回
每一句IL程式碼都加了註釋後,是不是覺得IL程式碼其實並不難呢,因為它的每一條指令都是固定的,你只要記住了,看IL程式碼就比較輕鬆了。
四 如何讀IL(深入瞭解)
4.1 提出問題
有了上面的一點IL基礎後,現在我們來深入一點點,
有如下幾個問題:
1 當 ldc.i4.1 這一指定載入 “i” 這個變數後並沒有馬上賦值給Record Frame中的元素,而是要執行 stloc.0 後才賦值,那沒賦值前是存在哪裡的呢?
2 ldloc.0 把元素取出來後,存在哪裡的?
3 add操作完成後值存在哪裡?
4.2 概念引入
Managed Heap::這是動態配置(Dynamic Allocation)的記憶體,由 Garbage Collector(GC)在執行時自動管理,整個 Process 共用一個
Managed Heap(我理解為託管堆,儲存引用型別的值)。
Evaluation Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Evaluation Stack(我理解為類似一個臨時存放值型別資料的執行緒棧)
Call Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Call Stack。每呼叫一次 method,就會使得 Call Stack 上多了一個 Record Frame;呼叫完畢之後,此 Record Frame 會被丟棄(我理解為一個區域性變量表,用於存放.locals init(int32 V_0)指令的引數值如:V_0)
4.3 IL指令詳解
對三個名詞做解釋後現在我們再來仔細看看執行IL指令時,對應的變數是如何存放的
IL_0001: ldc.i4.1 //載入第一個變數i 首先對 ldc.i4.1 做下細解:變數的值為1 時IL指令就是ldc.i4.1 ,變數值為2 時IL指令就是ldc.i4.2,依此類推一直到ldc.i4.8
當為-1 時IL指令為ldc.i4.M1,當超過8時就是一個統一指令 ldc.i4.S IL_0001: ldc.i4.1 //載入第一個變數i
當執行這一條指令時會把變數i 的值壓入Evaluation Stack中做臨時儲存
IL_0002: stloc.0 //把i 賦值給Call Stack中第0個位置 當執行這一條指信時會把Evaluation Stack 中的 i 彈出賦值給Record Frame中的第0個位置
IL_0007: ldloc.0 //取出Record Frame位置為0的元素 (i) 當執行這條指令時會將 Record Frame中的位置為0的元素的值取出(copy)壓入Evaluation Stack 等待做加法的指令 Add
IL_000b: add // 做加法操作 add這一操作完成後,會把結果存在Evaluation Stack中等待下一步的指令操作 4.4 問題回答 以上內容看完開始的問題相應也解決了 1 ldc.i4.1 把值取出來後先存在 Evaluation Stack中 執行了stloc.0 後才會存入Record Frame中指定的元素中 2 ldloc.0 把取出來後也是先壓入 Evaluation Stack 等持指令 3 add 操作完成後值是暫存於 Evaluation Stack中的 以上把IL指令是如何操作記憶體中的值做了一點很基本的介紹,讓大家在瞭解IL指令時,知道是如何操作記憶體中的值的。我想對於理解IL指令或許更透徹一點。
五 總結
這一篇只寫了IL中最基本的幾個指令,然後講解了IL指令是如何操作記憶體中資料的。古人云:水得一口一口喝,路得一步一步走,步子邁得大了容易扯著蛋,慢慢來內容雖然少了點,但是還會有下篇的。下一篇還是會寫IL的一些基本指令,我會結合我自己的理解,儘量把文字寫得通俗一點,讓大家更容易理解。 另外本人水平有限,難免會有理解錯誤的地方,如有發現,請指出!我會馬上修改,以免誤導他人。 如果您覺得本文能給您帶來一點收穫不妨點下 推薦 讓更多的人瞭解IL,您的推薦是我源源不斷的寫作力 如果覺得我的部落格還不錯,那就關個注吧~ 成長在於積累
參考資料:《你必須知道的.net》,MSDN
Record Frame
分類:
C#基礎知識
標籤:
IL 系列
好文要頂
關注我
收藏該文
Zery
關注 - 86
粉絲 - 616 +加關注 152 0 « 上一篇: C#操作XML方法集合
» 下一篇: 讀懂IL程式碼就這麼簡單(二)