學習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 類型)推送到計算堆棧上 |
未完待續......
文章列表