NET中IL理解(转)

.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 組合码。

时间: 2024-10-20 16:06:53

NET中IL理解(转)的相关文章

在实践中深入理解VMware虚拟机的上网模式:NAT模式

0.说明 本篇博文为<在实践中深入理解VMware虚拟机的上网模式>系列的其中一篇:NAT模式. VMware虚拟机在我们学习过程中必不可少,不管是安装Linux操作系统来进行学习.搭建各种网络服务,拟或者是用来做集群测试.云计算平台OpenStack等的搭建,可以说,VMware虚拟机为我们带来了极大的便利,最直观的好处就是,我们可以节省很多用来购买各种设备的资金,同时也可以达到了学习各种技术的目的. 然而只要涉及到通过宿主机与VMware虚拟机中的操作系统进行通信,或者是VMware虚拟机

ibatis 开发中的经验 (一)ibatis 和hibernate 在开发中的理解

这个项目的底层是用ibatis框架完毕,这几天也是都在用这个框架写代码,也有了一些简单的理解,把项目拿过来后基本的配置都已经配置好了,比方一些事务.日志控制等,在开发中主要用到的是写SQL语句以及熟悉ibatis xml文件都提供的一些标签,这些标签大部分是用来处理推断.逻辑,使得sql能够动态组装变的更灵活. 在写代码的工作量上ibatis要比hibernate高一些,在你写了服务层后还须要些dao层,dao层实现,然后这些在hibernate中不须要自己写,仅仅写到服务层就能够调用hiber

博客专题计划:《在实践中深入理解常见网络协议》

距离学习CCIE的课程已经有近一年的时间,虽然这一年来已经丢下了挺多关于路由交换技术的知识,不过随着这一年时间以来通过对Linux和Python的学习研究和学校相关课程的学习,对于TCP/IP的理解是越来越清晰,至少可以慢慢形成自己的想法,于是想借此机会,整理一下过去的思绪,撰写<在实践中深入理解常见网络协议>的博客专题. 写博客已有近一年的时间,慢慢地也形成了自己写博文的一种风格,有一大部分也获得了许多网友的肯定,包括51cto网友,或者通过其它方式浏览我写博文的其它门户网站的网友,在此表示

ibatis 和hibernate 在开发中的理解

这个项目的底层是用ibatis框架完成,这几天也是都在用这个框架写代码,也有了一些简单的理解,把项目拿过来后主要的配置都已经配置好了,比如一些事务.日志控制等,在开发中主要用到的是写SQL语句以及熟悉ibatis xml文件都提供的一些标签,这些标签大部分是用来处理判断.逻辑,使得sql可以动态组装变的更灵活. 在写代码的工作量上ibatis要比hibernate高一些,在你写了服务层后还需要些dao层,dao层实现,然后这些在hibernate中不需要自己写,只写到服务层就可以调用hibern

ios---&gt;OC中Protocol理解及在代理模式中的使用

OC中Protocol理解及在代理模式中的使用 Protocol基本概念 Protocol翻译过来, 叫做"协议",其作用就是用来声明一些方法: Protocol(协议)的作用 定义一套公用的接口(Public) @required:必须实现的方法,默认在@protocol里的方法都要求实现. @optional:可选实现的方法(可以全部都不实现) 委托代理(Delegate)传值 它本身是一个设计模式,它的意思是委托别人去做某事. 比如:两个类之间的传值,类A调用类B的方法,类B在执

(转)Python中如何理解if __name__ == &#39;__main__&#39;

摘要 通俗的理解 __name__ == '__main__' :假如你叫李凯.py,在朋友眼中,你是李凯( __name__ == '李凯' ):在你自己眼中,你是你自己( __name__ == '__main__' ). if __name__ == '__main__' 的意思是:当.py文件被直接运行时, if __name__ == '__main__' 之下的代码块将被运行:当.py文件以模块形式被导入时, if __name__ == '__main__' 之下的代码块不被运行.

vue中$event理解和框架中在包含默认值外传参

vue中$event理解和框架中在包含默认值外传参:https://blog.csdn.net/qq_43702430/article/details/90692242?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1 原文地址:https://

(二)STM32中中断优先级理解

很多人在配置STM32中断时对固件库中的这个函数NVIC_PriorityGroupConfig()——配置优先级分组方式,会很不理解,尤其是看中文翻译版的,因为中文翻译版里把这里翻译成“先占优先级和从优先级”这样翻译其实是不对的,很容易让人误解.为了便于大家理解,有必要先解释两个概念: 抢占式优先级/响应优先级: STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级. 具有高抢占式优

php中怎么理解Closure的bind和bindTo

bind是bindTo的静态版本,因此只说bind吧.(还不是太了解为什么要弄出两个版本) 官方文档: 复制一个闭包,绑定指定的$this对象和类作用域. 其实后半句表述很不清楚. 我的理解: 把一个闭包转换为某个类的方法(只是这个方法不需要通过对象调用), 这样闭包中的$this.static.self就转换成了对应的对象或类. 因为有几种情况: 1.只绑定$this对象.2.只绑定类作用域.3.同时绑定$this对象和类作用域.(文档的说法)4.都不绑定.(这样一来只是纯粹的复制, 文档说法