• .Net 中的IL中间语言基本语法


    一、前言

    IL是什么?

     Intermediate Language (IL)微软中间语言

    C#代码编译过程?

    C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语言指令;

    JIT编译器把IL代码转为机器识别的机器代码。如下图

    语言编译器:无论是VB code还是C# code都会被Language Compiler转换为MSIL

    MSIL的作用:MSIL包含一些元数据和中间语言指令

    JIT编译器的作用:根据系统环境将MSIL中间语言指令转换为机器码

    为什么ASP.NET网站第一次运行时会较慢,而后面的执行速度则会相对快很多?

    当你第一次运行.NET开发的站点时,CLR会将MSIL通过JIT进行编译,最终转换为执行速度非常快的Native Code。这可以解释。

    为什么要了解IL代码?

    如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以通过IL代码透过表面看本质;

    IL也是更好理解、认识CLR的基础;

    大量的实例分析是以IL为基础的,所以了解IL,是读懂他人代码的必备基础,同时自己也可以获得潜移默化的提高;

    二、如何把ILDasm导入到VS中

    想要看IL代码需要使用ILDasm工具,工具一般在电脑的

    C:Program Files (x86)Microsoft SDKsWindowsv7.0Ainildasm.exe

    也可以下载ILSpy:http://ilspy.net/

    把ILDasm导入到VS工具中,使用方便,具体如下:工具 - > 外部工具 

      

    导入之后,vs工具里面就有ILDasm工具了。以后想看IL代码方便多了。

    IL代码通过ILDasm反编译后(左图),ILDasm图标意义(右图)

      

    .NET CLR 和 Java VM 都是堆叠式虚拟机器(Stack-Based VM),也就是說,它們的指令集(Instruction Set)都是採用堆叠运算的方式:执行时的资料都是先放在堆叠中,再进行运算。JavaVM 有約 200 個指令(Instruction),每個指令都是 1 byte 的 opcode(操作码),后面接不等数目的参数;.NET CLR 有超過 220個指令,但是有些指令使用相同的 opcode,所以 opcode 的数目比指令数略少。特別注意,.NET 的 opcode 長度並不固定,大部分的 opcode 長度是 1 byte,少部分是 2 byte。

    下面是一個简单的 C# 原始码:                 

    复制代码代码如下:

    using System; 
    public class Test { 
        public static void Main(String[] args) { 
            int i=1; 
            int j=2; 
            int k=3; 
            int answer = i+j+k; 
            Console.WriteLine("i+j+k="+answer); 
        } 


    將此原始码编译之后,可以得到一個 EXE的程序。我們可以通过 ILDASM.EXE(图-0) 來反编译 EXE 以观察IL。我將 Main() 的 IL 反编译条列如下,這裡共有十八道IL 指令,有的指令(例如 ldstr 与 box)后面需要接参数,有的指令(例如 ldc.i4.1 與与add)后面不需要接参数。



    图-0
    ldc.i4.1
    stloc.0
    ldc.i4.2
    stloc.1
    ldc.i4.3
    stloc.2
    ldloc.0
    ldloc.1
    add
    ldloc.2
    add
    stloc.3
    ldstr      "i+j+k="
    ldloc.3
    box        [mscorlib]System.Int32
    call       string [mscorlib]System.String::Concat(object, object)
    call       void [mscorlib]System.Console::WriteLine(string)
    ret

    此程式执行時,关键的记忆体有三种,分別是:

    1、Managed Heap:這是动态配置(Dynamic Allocation)的记忆体,由 Garbage Collector(GC)在执行時自動管理,整個Process 共用一個 Managed Heap。

    2、Call Stack:這是由 .NET CLR 在执行時自動管理的记忆体,每個 Thread 都有自己专属的 Call Stack。每呼叫一次 method,就会使得Call Stack 上多了一個 Record Frame;呼叫完毕之后,此 Record Frame 会被丢弃。一般來說,Record Frame 內记录着 method 参数(Parameter)、返回位址(Return Address)、以及区域变数(Local Variable)。Java VM 和 .NET CLR 都是使用 0, 1, 2… 编号的方式來識別区别变数。

    3、Evaluation Stack:這是由 .NET CLR 在执行時自動管理的记忆体,每個 Thread 都有自己专属的 Evaluation Stack。前面所謂的堆叠式虚拟机器,指的就是這個堆叠。

    后面有一連串的示意图,用來解說在执行時此三种记忆体的变化。首先,在進入 Main() 之后,尚未执行任何指令之前,记忆体的狀況如图1 所示:

    對 1

    图1                

    接着要执行第一道指令 ldc.i4.1。此指令的意思是:在 Evaluation Stack 置入一個 4 byte 的常数,其值為 1。执行完此道指令之后,记忆体的变化如图2 所示:

    ldc.i4.1:表示加载一个值为1到堆栈中,该条指令的语法结构是:
    ldc.typevalue:ldc指令加载一个指定类型的常量到stack.
    ldc.i4.number:ldc指令更加有效.它传输一个整型值-1以及0到8之间的整数给计算堆栈

    對 2

    图2       

    接着要执行第二道指令 stloc.0。此指令的意思是:从 Evaluation Stack 取出一個值,放到第 0 号变数(V0)中。這裡的第 0 号变数其实就是原始码中的i。执行完此道指令之后,记忆体的变化如图3 所示:

    對 3

    图3                

    后面的第三道指令和第五道指令雷同於第一道指令,且第四道指令和第六道指令雷同於第二道指令。為了节省篇幅,我不在此一一贅述。提醒大家第 1 号变数(V1)其实就是原始码中的 j,且第 2 号变数(V2)其实就是源码中的 k。图4~7 分別是执行完第三~六道指令之后,记忆体的变化图:

    對 4

    图4                

    對 5

    图5

    對 6
    图6

    對 7
    图7

    接着要执行第七道指令 ldloc.0 以及第八道指令 ldloc.1:分別將 V0(也就是 i)和 V1(也就是 j)的值放到 Evaluation Stack,這是相加前的准备動作。图8 與图9 分別是执行完第七、第八道指令之后,记忆体的变化图:

    對 8

    图8

    對 9
    图9

    接着要执行第九道指令 add。此指令的意思是:从 Evaluation Stack 取出兩個值(也就是 i 和 j),相加之后將結果放回 Evaluation Stack 中。执行完此道指令之后,记忆体的变化如图10 所示:

    對 10
    图10

    接着要执行第十道指令 ldloc.2。此指令的意思是:分別將 V2(也就是 k)的值放到 Evaluation Stack,這是相加前的准备動作。执行完此道指令之后,记忆体的变化如图11 所示:

    對 11
    图11

    接着要执行第十一道指令 add。从 Evaluation Stack 取出兩個值,相加之后將結果放回 Evaluation Stack 中,此為 i+j+k 的值。执行完此道指令之后,记忆体的变化如图12 所示:

    對 12
    图12

    接着要执行第十二道指令 stloc.3。从 Evaluation Stack 取出一個值,放到第 3 号变数(V3)中。這裡的第3号变数其实就是原始码中的 answer。执行完此道指令之后,记忆体的变化如图13 所示:

    對 13
    图13

    接着要执行第十三道指令 ldstr "i+j+k="。此指令的意思是:將 "i+j+k=" 的 Reference 放進 Evaluation Stack。执行完此道指令之后,记忆体的变化如图14 所示:

    對 14
    图14

    接着要执行第十四道指令 ldloc.3。將 V3 的值放進 Evaluation Stack。执行完此道指令之后,记忆体的变化如图15 所示:

    對 15
    图15

    接着要执行第十五道指令 box [mscorlib]System.Int32,从此处可以看出,int到string实际是进行了装箱操作的,所以会有性能损失,可以在以后的编码中减少装箱操作来提高性能。此指令的意思是:从 Evaluation Stack 中取出一個值,將此 Value Type 包裝(box)成為 Reference Type。执行完此道指令之后,记忆体的变化如图16 所示:

    對 16
    图16

    接着要执行第十六道指令 call string [mscorlib] System.String::Concat(object, object)。此指令的意思是:从 Evaluation Stack 中取出兩個值,此二值皆為 Reference Type,下面的值当作第一個参数,上面的值当作第二個参数,呼叫 mscorlib.dll 所提供的 System.String.Concat() method 來將此二参数進行字串接合(String Concatenation),將接合出來的新字串放在 Managed Heap,將其 Reference 放進 Evaluation Stack。值得注意的是:由於 System.String.Concat() 是 static method,所以此處使用的指令是 call,而非 callvirt(呼叫虚拟)。执行完此道指令之后,记忆体的变化如图17 所示:

    對 17
    图17

    請注意:此時 Managed Heap 中的 Int32(6) 以及 String("i+j+k=") 已經不再被參考到,所以变成垃圾,等待 GC 的回收。

    接着要执行第十七道指令 call void [mscorlib] System.Console::WriteLine(string)。此指令的意思是:从 Evaluation Stack 中取出一個值,此值為 Reference Type,將此值当作参数,呼叫 mscorlib.dll 所提供的 System.Console.WriteLine() method 來將此字串显示在 Console 視窗上。System.Console.WriteLine() 也是 static method。执行完此道指令之后,记忆体的变化如图18 所示:

    對 18图18

    接着要执行第十八道指令 ret。此指令的意思是:結束此次呼叫(也就是 Main 的呼叫)。此時会檢查 Evaluation Stack 內剩下的資料,由於 Main() 宣告不需要传出值(void),所以 Evaluation Stack 內必須是空的,本范例符合這樣的情況,所以此時可以順利結束此次呼叫。而 Main 的呼叫一結束,程式也随之結束。执行完此道指令之后(且在程式結束前),记忆体的变化如图19 所示:

    對 19图19

    通过此范例,读者应该可以对于 IL 有最基本的认识。对 IL 感兴趣的读者应该自行阅读 Serge Lidin 所著的《Inside Microsoft .NET IL Assembler》(Microsoft Press 出版)。我认为:熟知 IL 每道指令的作用,是 .NET 程式員必备的知识。.NET 程式員可以不会用 IL Assembly 写程式,但是至少要看得懂 ILDASM 反编译出來的 IL 組合码。

  • 相关阅读:
    Android TouchEvent 分发流程
    python基础7之python中常用的模块的总结
    C# 压缩数据传输
    C# winFrom 加载BMP 底图
    使用jQuery Ajax功能的时候需要注意的一个问题
    jQuery DOM的操作
    C# CookieExtension 使用Cookie的扩展工具类
    Web Service测试工具小汇 转
    C# 把DT 的数据转换成 list<Model> EntityByEmit
    Web下 MD5 加密与解密
  • 原文地址:https://www.cnblogs.com/ZengYunChun/p/6125500.html
Copyright © 2020-2023  润新知