學習Emit必不可少的, 會使用到IL中間代碼. 初見IL代碼, 讓我有一種彙編的感覺, 讓我想起了, 大學時, 學習8051的彙編語言. 多的就不扯了, 直接進入正題, OpCodes指令集是不是有一種讓人望而卻步的感覺, 那麼多, 具體我沒有數過, 但是肯定是比8051的指令多不少, 應該有20 ...
學習Emit必不可少的, 會使用到IL中間代碼. 初見IL代碼, 讓我有一種彙編的感覺, 讓我想起了, 大學時, 學習8051的彙編語言. 多的就不扯了, 直接進入正題, OpCodes指令集是不是有一種讓人望而卻步的感覺, 那麼多, 具體我沒有數過, 但是肯定是比8051的指令多不少, 應該有200多個吧, 不過在實際使用的過程中, 肯定是用不到這麼多的, 所以只要掌握一些常用的就夠用了, 其餘的, 查資料就可以了(大學老師當時也是這麼教的, 實際使用中, 也確實是這樣的)
一、示例
上一篇的結束部分, 貼出了一個中文註釋版的OpCodes文件, 這部分內容跟那個文件是有很大關聯的. 貌似, 貼在這一篇更合適呢...
嗯, 還是應該從示例里去開始講
來源 : http://www.cnblogs.com/zery/p/3366175.html
static void Sum(int sum, string sumStr) { int a, b, c; a = 1; b = 2; c = 3; sum = a + b + c; sumStr = sum.ToString(); Console.WriteLine(sumStr); for (int i = 0; i < c; i++) { if (i > b) { Console.WriteLine("滿足條件, 跳出迴圈"); break; } } Console.ReadKey(); }
.method private hidebysig static void Sum(int32 sum, string sumStr) cil managed { .maxstack 2 //定義函數代碼所用堆棧的最大深度,也指Evaluation Stackk中最多能同時存在2個值 .locals init ( //變數的聲明, (此時已經把num,num2,num3,num4,flag存入了Call Stack中的Record Frame中) [0] int32 num, [1] int32 num2, [2] int32 num3, [3] int32 num4, [4] bool flag) L_0000: nop //無任何操作, 可忽略 L_0001: ldc.i4.1 //載入 常量1 到棧中(壓棧) L_0002: stloc.0 //從棧中把 常量1 拿出來, 賦值給num(出棧, 此時棧中已經沒有東西了) L_0003: ldc.i4.2 //載入 常量2 到棧中(壓棧) L_0004: stloc.1 L_0005: ldc.i4.3 L_0006: stloc.2 L_0007: ldloc.0 //將num變數壓棧 L_0008: ldloc.1 //將變數num2壓棧 (此時棧中有兩個值, num2在上面, num在下麵) L_0009: add //將num,num2求和的結果壓棧(求和的時候, 會把兩個值都提取出來, 所以結束後, 棧中只有一個結果值) L_000a: ldloc.2 //將num3壓棧 L_000b: add //將num3,(num+num2)求和, 並壓棧, 此時棧中, 只有最後的結果值 L_000c: starg.s sum //將棧頂的值傳給傳參sum(短格式) L_000e: ldarga.s sum //載入sum的地址到堆棧上(短格式) L_0010: call instance string [mscorlib]System.Int32::ToString() //調用ToString()方法, 完成格式轉換,將結果值放入堆棧中 L_0015: starg.s sumStr //將堆棧頂的值傳給傳參sumStr(短格式) L_0017: ldarg.1 //將索引為1的傳參(sumStr)載入到堆棧中 L_0018: call void [mscorlib]System.Console::WriteLine(string) //調用Console.WriteLine方法 L_001d: nop L_001e: ldc.i4.0 L_001f: stloc.3 // i = 0 L_0020: br.s L_0043 //無條件跳轉到下麵, 去判斷 i<c 是否成立 L_0022: nop L_0023: ldloc.3 // i L_0024: ldloc.1 // b L_0025: cgt // i > b ? 1 : 0 L_0027: ldc.i4.0 //壓棧0 L_0028: ceq //比較的結果在與0比較, (i > b ? 1 : 0) == 0 ? 1 : 0 L_002a: stloc.s flag //將結果存入本地變數flag L_002c: ldloc.s flag //載入flag到堆棧中 L_002e: brtrue.s L_003e //為真跳轉到 L_003e L_0030: nop L_0031: ldstr "\u6ee1\u8db3\u6761\u4ef6, \u8df3\u51fa\u5faa\u73af" //"滿足條件, 跳出迴圈" L_0036: call void [mscorlib]System.Console::WriteLine(string) L_003b: nop L_003c: br.s L_004d L_003e: nop L_003f: ldloc.3 // i L_0040: ldc.i4.1 // 1 L_0041: add // i + 1 L_0042: stloc.3 // i = i + 1 L_0043: ldloc.3 // i L_0044: ldloc.2 //c L_0045: clt // i < c ? 1 : 0 L_0047: stloc.s flag //將結果傳給 flag L_0049: ldloc.s flag //載入flag變數到堆棧中 L_004b: brtrue.s L_0022 //為真跳轉 L_0022 L_004d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() L_0052: pop //移除當前位於計算堆棧頂部的值 L_0053: ret //即為 return 標記 返回值 }
ldc.i4.1: i4--int32, 1--數值, 合起來就是 載入int32的數值1到堆棧中
stloc.0: 0--前面聲明的locals變數組中的第0個 將堆棧頂的值付給locals0變數
ldloc.0: 載入locals0變數到堆棧中
add : 將棧頂的兩個值求和, 並將結果壓棧
二、常用的指令
維基百科:https://en.wikipedia.org/wiki/List_of_CIL_instructions
來源:http://blog.csdn.net/joyhen/article/details/47276433
1. 常用的載入類指令
ldarg (及多個變化形式) |
ld -- load , arg -- argument, 對這個大家都不陌生吧, 就不多解釋了 載入方法的參數的值到棧中。除了泛型ldarg(需要一個索引作為參數),還有後其他很多的變化形式。'.'有個數字尾碼的ldarg操作碼來指定需要載入的參數。 a -- address, s -- short ldarga/ldarga.s表示的是載入參數的地址, 而不是載入參數的值 |
ldc (及多個變化形式) |
c -- constant, const這個關鍵字大家肯定都很熟了, constant表示常量 載入一個常數到棧中 Ldc.I4.2 i4表示是int32的值(1個表示8位), 2表示常量 |
ldfld (及多個變化形式) | 載入一個對象實例的成員到棧中 |
ldloc (及多個變化形式) |
loc -- locals 載入一個本地變數到棧中 |
ldobj | 獲得一個堆對象的所有數據,並將它們放置到棧中. OpCodes:將地址指向的值類型對象複製到計算堆棧的頂部。 |
ldstr | 載入一個字元串數據到棧中 |
2. 常用的彈出操作指令
pop | 刪除當前棧頂的值,但是並不影響存儲的值 |
starg |
st -- store 存儲棧頂的值到給出方法的參數,根據索引確定這個參數. OpCodes:將位於計算堆棧頂部的值存儲到位於指定索引的參數槽中 |
stloc (及多個變化形式) | 彈出當前棧頂的值並存儲在一個本地變數列表中,根據所以確定這個參數 |
stobj | 從棧中複製一個特定的類型到指定的記憶體地址 |
stfld | 用從棧中獲得的值替換對象成員的值 |
3. 常用的其他操作類指令
add, sub, mul, div, rem |
用於兩個數加減乘除求模, 並將結果推送到計算堆棧上 |
and, or, not, xor | 用於在兩個值上進行二進位操作 |
ceq, cgt, clt |
用不同的方法比較兩個在棧上的值 c -- compare ceq:是否相等 -- 如果這兩個值相等,則將整數值 1 (int32) 推送到計算堆棧上;否則,將 0 (int32) 推送到計算堆棧上 cgt:是否大於 -- 如果第一個值大於第二個值,則將整數值 1 (int32) 推送到計算堆棧上;反之,將 0 (int32) 推送到計算堆棧上。 cgt.un -- 比較兩個無符號的或不可排序的值, un -- unsigned 無符號 clt:是否小於 -- 如果第一個值小於第二個值,則將整數值 1 (int32) 推送到計算堆棧上;反之,將 0 (int32) 推送到計算堆棧上。 |
box, unbox |
在引用類型和值類型之間轉換 box: 裝箱 unbox: 拆箱 |
ret | 退出方法和返回一個值 |
beq, bgt,bge,ble, blt, switch |
控制方法中的條件分支 b -- break, eq,e -- equal beq:如果兩個值相等,則將控制轉移到目標指令; bgt:如果第一個值 > 第二個值,則將控制轉移到目標指令 bge:如果第一個值 >= 第二個值,則將控制轉移到目標指令 ble:如果第一個值 <= 第二個值,則將控制轉移到目標指令 blt:如果第一個值 < 第二個值,則將控制轉移到目標指令 switch:實現跳轉表 所有的分支控制操作碼都需要給出一個CIL代碼標簽作為條件為真的跳轉目的地 |
brtrue |
如果 value 為 true、非空或非零,則將控制轉移到目標指令 |
br/br.s |
br:(無條件)中止到代碼標簽 br.s:無條件地將控制轉移到目標指令(短格式) |
call | 調用一個成員 |
newarr, newobj |
在記憶體中創建一個新的數組或新的對象類型 newarr:將對新的從零開始的一維數組(其元素屬於特定類型)的對象引用推送到計算堆棧上 newobj:創建一個值類型的新對象或新實例,並將對象引用(O 類型)推送到計算堆棧上 |
未完待續......