讀懂IL代碼就這么簡單(三)完結篇


一 前言

寫了兩篇關於IL指令相關的文章,分別把值類型與引用類型在 堆與棧上的操作區別詳細的寫了一遍
這第三篇也是最后一篇,之所以到第三篇就結束了,是因為以我現在的層次,能理解到的都寫完了,而且個人認為,重要的地方都差不多
寫到了,
最后一篇決定把之前的內容全部整合起做一個綜合的例子,然后簡單的解釋下IL指令的含義,及在內存中的變化
如果你沒有看前兩篇請狂點這里


讀懂IL代碼就這么簡單 (一)

讀懂IL代碼就這么簡單(二)

 

IL指令大全 :IL指令詳解

 

IL反編譯工具: ILDasm

 

 注:因本人水平有限,難免有理解錯誤之處,如有發現,望及時指出,我會立馬更正。

二 IL指令詳解 (基本介紹)

這次把 類 委托 方法 字段都集合起來,這樣的環境就與實際的項目比較接近了,也算接地氣了

先看C#代碼

 1     public delegate void MyDele(string name);
 2     class Program
 3     {
 4         static void Main(string[] args)
 5         {
 6             
 7             UserInfo userInfo = new UserInfo();
 8         
 9             PeopleStruct peopleStruct = new PeopleStruct();
10 
11             //定義委托
12             MyDele myDele = userInfo.PrintName;
13             //調用委托
14             myDele("Delegate");
15 
16             userInfo.PrintName("PrintName");
17             userInfo.PrintField();
18             //靜態方法 
19             UserInfo.ContactStr("UserInfo", "ContactStr");
20             //結構的方法
21             peopleStruct.PrintInfo("Color is Yellow");
22 
23             //靜態類中的靜態方法 
24             StaticUserInfo.PrintName("Static Class Static Method");
25 
26             Console.Read();
27         }
28     }
29 
30     internal class UserInfo
31     {
32         public string Name = "UserInfo Field";
33 
34         public void PrintName(string name)
35         {
36             Console.WriteLine(name);
37         }
38 
39         public void PrintField()
40         {
41             Console.WriteLine(Name);
42         }
43 
44         public static void ContactStr(string Str, string Str2)
45         {
46             Console.WriteLine(Str + Str2);
47         }
48 
49     }
50 
51     struct PeopleStruct
52     {
53 
54         public void PrintInfo(string color)
55         {
56             Console.WriteLine(color);
57         }
58 
59     }
60 
61     static class StaticUserInfo
62     {
63         public static void PrintName(string name)
64         {
65             Console.WriteLine(name);
66         }
67     }

IL 代碼 

call可以調用靜態方法,實例方法和虛方法

callvirt只能調用實例方法和虛方法,不能調用靜態方法

 1 .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint
 4   // Code size       106 (0x6a)
 5   .maxstack  2
 6   .locals init (class ILDeom3.UserInfo V_0, //只定義變量並不做任何初始化操作
 7            valuetype ILDeom3.PeopleStruct V_1,
 8            class ILDeom3.MyDele V_2)
 9   IL_0000:  nop
10       //創建一個值類型的新對象或新實例,並將對象引用推送到計算堆棧上
11   IL_0001:  newobj     instance void ILDeom3.UserInfo::.ctor()
12       //把棧中頂部的元素彈出(UserInfo 的實例)並賦值給局部變量表中第0個位置的元素(V_0)
13   IL_0006:  stloc.0
14       //將位於特定索引處的局部變量的 "地址" 加載到計算堆棧上(將指向結構的地址壓入棧中)
15   IL_0007:  ldloca.s   V_1
16       //初始化結構中的屬性
17   IL_0009:  initobj    ILDeom3.PeopleStruct
18       //將局部變量列表中第0個位置(V_0 UerInfo的實例地址)的值壓入棧中
19   IL_000f:  ldloc.0
20       //將指向實現特定方法的本機代碼的非托管指針(native int 類型)推送到計算堆棧上。
21       //也就是指的將方法指針壓入棧中
22   IL_0010:  ldftn      instance void ILDeom3.UserInfo::PrintName(string)
23      //創建委托的實例並壓入棧中  
24      //這一步會調用委托的構造器,這個構造器需要兩個參數,一個對象引用,就是IL_000f:  ldloc.0壓入的UserInfo的實例,一個方法的地址。
25   IL_0016:  newobj     instance void ILDeom3.MyDele::.ctor(object,native int)
26       //彈出棧中值(委托的實例)保存到局部變量表第2個位置(V_2)
27   IL_001b:  stloc.2
28       //獲取局部變量列表中第2個位置上的值上一步保存的值(委托實例),並壓入棧中
29   IL_001c:  ldloc.2
30       //加載字符串
31   IL_001d:  ldstr      "Delegate"
32       //調用綁定給委托的PrintName方法 
33   IL_0022:  callvirt   instance void ILDeom3.MyDele::Invoke(string)
34   IL_0027:  nop
35       //獲取局部變量列表中第0個位置上的值(UserInfo的實例)
36   IL_0028:  ldloc.0
37   IL_0029:  ldstr      "PrintName"
38      //調用PrintName方法
39   IL_002e:  callvirt   instance void ILDeom3.UserInfo::PrintName(string)
40   IL_0033:  nop
41       //獲取局部變量列表中第0個位置上的值(UserInfo的實例)
42   IL_0034:  ldloc.0
43       //調用PrintField方法
44   IL_0035:  callvirt   instance void ILDeom3.UserInfo::PrintField()
45   IL_003a:  nop
46   IL_003b:  ldstr      "UserInfo"
47   IL_0040:  ldstr      "ContactStr"
48       //因為ContactStr是靜態方法所以不需要先加載實例可以直接調用
49   IL_0045:  call       void ILDeom3.UserInfo::ContactStr(string,
50                                                          string)
51   IL_004a:  nop
52       //將位於特定索引處的局部變量的 "地址" 加載到計算堆棧上 (將指向結構的地址壓入棧中)
53   IL_004b:  ldloca.s   V_1
54   IL_004d:  ldstr      "Color is Yellow"
55       //調用結構中的PrintInfo方法
56   IL_0052:  call       instance void ILDeom3.PeopleStruct::PrintInfo(string)
57   IL_0057:  nop
58   IL_0058:  ldstr      "Static Class Static Method"
59   IL_005d:  call       void ILDeom3.StaticUserInfo::PrintName(string)
60   IL_0062:  nop
61   IL_0063:  call       int32 [mscorlib]System.Console::Read()
62   IL_0068:  pop
63   IL_0069:  ret
64 } // end of method Program::Main

 

相信有注釋,大家應該都是能夠看懂的,IL其實並不難,也並不算底層,只是把C#編譯成了中間語言,並非機器語言,CPU照樣還是讀不懂,

 

三 IL指令詳解 (深入了解)

 

因這次IL指令,有點長,要畫圖確實有點扛不住,所以只畫重要的地方,還望見諒.

另外 跟園子里的 @冰麟輕武 探討了跟IL相關的三個內存塊 Managed Heap ,Evaluation Stack,Call Stack 了解到了很多之前不明白的知識點,

也糾正了自己以前的一些誤區,最后一致認可我們自己的討論結果,討論結果如下,

1 Managed Heap(托管堆) 程序運行時會動態的在其中開辟空間來存儲變量的值,如new class 時,回收由GC 根據 代齡,和可達對象,來回收相應的內存資源。整個程序共用一個ManagedHeap 

2 Evaluation Stack(計算棧):每個線程都有一個獨立的 評估棧,用於程序相關的運算,

3 Call Stack(調用棧):討論的重點就在這里,之前認為Call Stack並不是一個棧,而是一個局部變量列表,用於存放方法的參數,可是我一直有疑問就是值類型應該是存在棧中的,如果Call Stack是個棧,那取值時Call Stack並沒有按FILO的原則來,那如果 Call Stack不是個棧那值類型的值 是存在哪里的,然后我與@冰麟輕武就這一問題,討論起來了

  先看官方對Call Stack的解釋: 這是由.NET CLR在執行時自動管理的存儲單元,每個Thread都有自己專門的Call Stack。每呼叫一次method,就會使得Call Stack上多一個Record Frame;方法執行完畢之后,此Record Frame會被丟棄重點就在紅色這一句中的 Record Frame又是個什么東西他里邊有什么東西?然后開始各種假設,最終我們認為這一種理論是比較靠譜一點的如下:

  Call Stack本身就是一個棧,每調用一個方法時就會在棧頂部加載一個Record Frame,這個Record Frame里包含了方法所需要的參數(Params),返回地址(Return Address)和區域變量(Local Variable),當調用的方法結束時,就自動會把這個Record Frame從棧頂彈出。如此一來,我之前的疑問就可以得到相應的解釋了

  值類型是存在棧中的,當調用方法里會把方法需要的值重棧中取出,然后在棧中創建一個Record Frame並把賦值給Record Frame中的參數,在這個Record Frame中取數據並不是按FILO原則來的,而可以按索引,也可以按地址 對應IL指令 Ldloc stLoc 等取值與賦值都是針對的Record Frame 。而且我們認為Call Stack是對線程棧的一個統稱。

上圖

 

 

下面圖解一下實例化一個類,並調用類中的方法在內存中是如何變化的 

 

.locals init (class ILDeom3.UserInfo V_0,valuetype ILDeom3.PeopleStruct V_1,class ILDeom3.MyDele V_2)

 IL_0001:  newobj     instance void ILDeom3.UserInfo::.ctor()

 IL_0006:  stloc.0

 

 IL_0028:  ldloc.0

 IL_0029: ldstr "PrintName"
 IL_002e: callvirt instance void ILDeom3.UserInfo::PrintName(string)

 

 

四 總結

  IL系列終於寫完了,也算給自己一個交代了,寫文章真的很花時間,就以我這三篇為例,光只是寫和畫圖都有花十幾個小時,而且如果是晚上寫一般都會超過12點才能完成,更不用說前期的自己學習所用的時間,

但是我覺得真的很值得,充分的把自己的業余時間利用起來了,對於IL也有了一個相對深入的了解,

在此要感謝 園子里朋友的支持,也感謝 @冰麟輕武對我的指點,更要感謝dudu能建立博客園這么好的一個環境。

 

 

 

如果您覺得本文有給您帶來一點收獲,不妨點個推薦,為我的付出支持一下,謝謝~

如果希望在技術的道路上能有更多的朋友,那就關注下我吧,讓我們一起在技術的路上奔跑

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM