一、前言

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 SDKs\Windows\v7.0A\bin\ildasm.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

接着要执行第一道指令 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

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

图3

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

图4

图5


图6


图7

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

图8


图9

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


图10

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


图11

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


图12

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


图13

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


图14

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


图15

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

請注意:此時 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

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

图19

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

最新文章

  1. tomcat之Session的管理
  2. [make]makefile使用积累
  3. 关于iframe的滚动条,如何去掉水平滚动条或垂直滚动条
  4. php Memcache/Memcached操作手册
  5. kuangbin_ShortPath J (POJ 1511)
  6. kettle实现文本文件数据抽取方法
  7. 使用TreeView+ListBox+TxtBox 资料管理器
  8. VS2005配置CPPUnit进行单元測试
  9. JQ 遍历节点
  10. 位运算之 C 与或非异或
  11. BeautifulSoup学习 之结构
  12. HTML5 前端将 dom 元素转化为 Word,EXCEL 或者图片 并实现下载
  13. scrapy 选择器
  14. mvc webapi路由重写
  15. android 使用webview 加载网页
  16. (转)代号为Purley的新一代服务器平台
  17. Redis的安装与部署(CentOS6.5)
  18. The Qt Resource System
  19. linux配置免密登录
  20. Thrift编译错误('::malloc' has not been declared)

热门文章

  1. C语言高级宏技巧
  2. java项目地址和服务器地址区分
  3. Oracle事务和锁机制
  4. 使用 getopt 处理命令行长参数
  5. C# 字符串中正则表达式的应用
  6. Python3.x:pyodbc连接Sybase数据库操作(Windows系统下DNS模式)
  7. 手动漏洞挖掘-SQL注入(安全牛笔记)
  8. 本地存储(localStorage、sessionStorage、web Database)
  9. [nowcoder]因数个数和
  10. redis入门笔记