文章出處

學習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 類型)推送到計算堆棧上

 

未完待續......


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()