1. 程式人生 > >讀懂IL程式碼就這麼簡單(二)

讀懂IL程式碼就這麼簡單(二)

一 前言

  IL系列 第一篇寫完後 得到高人指點,及時更正了文章中的錯誤,也使得我寫這篇文章時更加謹慎,自己在瞭解相關知識點時,也更為細緻。個人覺得既然做為文章寫出來,就一定要保證比較高的質量,和正確率 。感謝 @冰麟輕武 的指點

知識點回顧:

Managed Heap(託管堆):用於存放引用型別的值

Evaluation Statck(計算棧):臨時存放值型別資料,引用型別地址的堆疊(這個是棧,所以遵循棧的操作特點,先進後出)

Call Stack(呼叫棧):其中的Record Frame 用於存放.locals init(int32 V_0)指令的引數值如:V_0 (Record Frame是一個區域性變量表,所以不遵守FILO原則 )

二 指令詳解(基本介紹)

2.1 知識點介紹

  在第一篇時,我只詳細的寫了值型別的IL指令,這一篇會主要以引用型別為主,這一篇會有裝箱操作,所以先寫一下裝箱操作在記憶體中是如何操作的

裝箱操作:1 記憶體分配,在託管堆中分配記憶體空間,2 將值型別的欄位拷貝到新分配的記憶體中,3 將託管堆中的物件地址返回給新的物件

操作過程如下圖

C#程式碼 

複製程式碼

 1         /*
 2          Author:zery-zhang
 3          BlogAddress:http://www.cnblogs.com/zery/
 4          */
 5         static void Main(string[] args)
 6         {
 7             string name = "Zery";
 8             int age = 22;
 9             Console.WriteLine(age.ToString() + name);//已ToString的操作
10             Console.WriteLine(age+name);//未ToString操作
11 
12         }

複製程式碼

IL程式碼 

複製程式碼

 1     /*
 2         Author:zery-zhang
 3         BlogAddress:http://www.cnblogs.com/zery/
 4     */
 5     .method private hidebysig static void  Main(string[] args) cil managed
 6 {
 7   .entrypoint
 8   // Code size       48 (0x30)
 9 
10  //以下程式碼 完成 C#程式碼中初始化變數的操作
11 
12   //計算棧(Evaluation Stack) 可容納資料項的最大個數
13   .maxstack  2              
14    //定義並初始化引數 並存入 區域性變量表(Record Frame)中
15   .locals init (string V_0,int32 V_1) 
16   IL_0000:  nop
17   //把字串壓入計算棧(Evaluation Stack)中
18   IL_0001:  ldstr      "Zery"   
19   // 從計算棧中彈出("Zery")字元,並賦值給區域性變量表中第0個位置的元素V_0
20   IL_0006:  stloc.0       
21   //把整數22壓入計算棧中  
22   IL_0007:  ldc.i4.s   22    
23   //把整數22彈出,並賦值給區域性變量表中第1個位置的元素V_1
24   IL_0009:  stloc.1             
25 
26   //以下程式碼完成C#中的輸出操作
27 
28   //取出區域性變量表中V_1元素的值 "22" (copy)並壓入計算棧中
29   IL_000a:  ldloca.s   V_1 
30   //彈出剛剛壓入的值("22")呼叫ToString方法轉成string型別並將引用存入計算棧中
31   IL_000c:  call       instance string [mscorlib]System.Int32::ToString() 
32    //取出區域性變量表中第0個位置元素(V_0)的值("Zery")壓入計算棧中(此時計算棧中有兩個值,指向推管堆中"22"的引用地址和字串"Zery")
33   IL_0011:  ldloc.0            
34    //彈出計算棧中兩個值呼叫String的Concat方法把字個字元拼接存入托管堆中(Managed Heap )並返回地址壓入計算棧中
35   IL_0012:  call       string [mscorlib]System.String::Concat(string,string)   
36    //呼叫輸出方法,呼叫輸出方法後計算棧中的值(指向託管堆字元的地址)會被回收 。
37   IL_0017:  call       void [mscorlib]System.Console::WriteLine(string) 
38 
39   //未ToString的操作
40   IL_001c:  nop
41    //取區域性變量表中第1個位置的元素V_1的值("22") 壓入計算棧中
42   IL_001d:  ldloc.1              
43    //把剛剛壓入的整數22 裝箱並返回指向託管堆的地址存入計算棧中
44   IL_001e:  box        [mscorlib]System.Int32   
45    //取區域性變量表中第0個位置的f元素V_0的值("Zery")並壓入計算棧中
46   IL_0023:  ldloc.0              
47    //彈出計算棧中兩個值呼叫String的Concat方法把字個字元拼接存入托管堆中(Managed Heap )並返回地址壓入計算棧中
48   IL_0024:  call       string [mscorlib]System.String::Concat(object,object)   
49    //呼叫輸出方法
50   IL_0029:  call       void [mscorlib]System.Console::WriteLine(string)        
51   IL_002e:  nop
52    //標記返回
53   IL_002f:  ret               
54 } // end of method Program::Main

複製程式碼

2.2 IL指令詳解

  .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:  呼叫指定的方法

  box:執行裝箱操作

  ret: 即return  標記返回

三 指令詳解(深入介紹)

如果看程式碼中的註釋你還不是很理解,那就看看下面的圖解過程吧,如果每一步都畫圖,那工程太大了,所以我會把簡單的的步組合成一張圖並做上註釋

先畫初始化的程式碼詳解圖  注:為了減少圖片所以棧的彈出與壓入操作就省去了,都只畫出了結果

IL_0001: ldstr "Zery"
IL_0006: stloc.0
IL_0007: ldc.i4.s 22
IL_0009: stloc.1

因為字串是引用型別,所以是儲存在託管堆中,而棧中只儲存對字元引用的地址,可以看到圖中的字串是在託管中的,而計算棧中只儲存了引用

IL_000a: ldloca.s V_1
IL_000c: call instance string [mscorlib]System.Int32::ToString()
IL_0011: ldloc.0
IL_0012: call string [mscorlib]System.String::Concat(string,string)
IL_0017: call void [mscorlib]System.Console::WriteLine(string)

IL_001d: ldloc.1
IL_001e: box [mscorlib]System.Int32
IL_0023: ldloc.0
IL_0024: call string [mscorlib]System.String::Concat(object,
object)
IL_0029: call void [mscorlib]System.Console::WriteLine(string)

裝箱的過程圖在上面已給出此處只把結果畫出

四 總結

  1 用兩篇把 值型別與引用型別在記憶體中不同的位置與不同的操作詳細的寫了,我覺得還是很有必要的,因為所有的資料型別由這兩種型別組成,把這兩種型別的操作了解了

看其它IL指令就是透過本質看現象了。

     2  關於畫圖,我覺得畫圖是程式設計師必學的知識,牛X的程式設計師畫出來的,系統架構圖,系統設計圖等都是很有結構很清晰的。我的畫圖技能還有太多不足,只是畫這種簡單的圖都覺得,無法完美的表達自己頭腦所想的那樣,