接触NET也有年左右的时间了NET的内部实现对我产生了很大的吸引力个人觉得能对这些底部的实现进行了解和熟练的话对以后自己写代码是有很大帮助的好了废话不多说请看下边
NET CLR 和 Java VM 都是堆叠式虚拟机器(StackBased VM)也就是说它们的指令集(Instruction Set)都是採用堆叠运算的方式执行时的资料都是先放在堆叠中再进行运算JavaVM 有约 个指令(Instruction)每个指令都是 byte 的 opcode(操作码)后面接不等数目的参数NET CLR 有超过 个指令但是有些指令使用相同的 opcode所以 opcode 的数目比指令数略少特别注意NET 的 opcode 长度并不固定大部分的 opcode 长度是 byte少部分是 byte 下面是一个简单的 C# 原始码 复制代码 代码如下: using System; public class Test { public static void Main(String[] args) { int i=; int j=; int k=; int answer = i+j+k; ConsoleWriteLine("i+j+k="+answer); } } 将 此原始码编译之后可以得到一个 EXE的程序我们可以通过 ILDASMEXE(图) 来反编译 EXE 以观察IL我将 Main() 的 IL 反编译条列如下这里共有十八道IL 指令有的指令(例如 ldstr 与 box)后面需要接参数有的指令(例如 ldci 与与add)后面不需要接参数 图 ldci stloc ldci stloc ldci stloc ldloc ldloc add ldloc add stloc ldstr "i+j+k=" ldloc box [mscorlib]SystemInt call string [mscorlib]SystemString::Concat(object object) call void [mscorlib]SystemConsole::WriteLine(string) ret
此程式执行时关键的记忆体有三种分别是
Managed Heap这是动态配置(Dynamic Allocation)的记忆体由 Garbage Collector(GC)在执行时自动管理整个Process 共用一个 Managed Heap 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 都是使用 … 编号的方式来识别区别变数 Evaluation Stack这是由 NET CLR 在执行时自动管理的记忆体每个 Thread 都有自己专属的 Evaluation Stack前面所谓的堆叠式虚拟机器指的就是这个堆叠 后面有一连串的示意图用来解说在执行时此三种记忆体的变化首先在进入 Main() 之后尚未执行任何指令之前记忆体的状况如图 所示 图 接着要执行第一道指令 ldci此指令的意思是在 Evaluation Stack 置入一个 byte 的常数其值为 执行完此道指令之后记忆体的变化如图 所示 ldci表示加载一个值为到堆栈中该条指令的语法结构是 ldctypevalueldc指令加载一个指定类型的常量到stack ldcinumberldc指令更加有效它传输一个整型值以及到之间的整数给计算堆栈 图 接着要执行第二道指令 stloc此指令的意思是从 Evaluation Stack 取出一个值放到第 号变数(V)中这里的第 号变数其实就是原始码中的i执行完此道指令之后记忆体的变化如图 所示 图 后面的第三道指令和第五道指令雷同于第一道指令且第四道指令和第六道指令雷同于第二道指令为了节省篇幅我不在此一一赘述提醒大家第 号变数(V)其实就是原始码中的 j且第 号变数(V)其实就是源码中的 k图~ 分别是执行完第三~六道指令之后记忆体的变化图 图 图
图 图 接着要执行第七道指令 ldloc 以及第八道指令 ldloc分别将 V(也就是 i)和 V(也就是 j)的值放到 Evaluation Stack这是相加前的准备动作图 与图 分别是执行完第七第八道指令之后记忆体的变化图 图 图 接着要执行第九道指令 add此指令的意思是从 Evaluation Stack 取出两个值(也就是 i 和 j)相加之后将结果放回 EvaluationStack 中执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十道指令 ldloc此指令的意思是分别将 V(也就是 k)的值放到 Evaluation Stack这是相加前的准备动作执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十一道指令 add从 Evaluation Stack 取出两个值相加之后将结果放回 Evaluation Stack 中此为 i+j+k 的值执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十二道指令 stloc从 Evaluation Stack 取出一个值放到第 号变数(V)中这里的第号变数其实就是原始码中的 answer执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十三道指令 ldstr "i+j+k="此指令的意思是将 "i+j+k=" 的 Reference 放进 Evaluation Stack执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十四道指令 ldloc将 V 的值放进 Evaluation Stack执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十五道指令 box [mscorlib]SystemInt从此处可以看出int到string实际是进行了装箱操作的所以会有性能损失可以在以后的编码中减少装箱操作来提高性能此指令的意思是从 Evaluation Stack 中取出一个值将此 Value Type 包装(box)成为 Reference Type执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十六道指令 call string [mscorlib] SystemString::Concat(object object)此指令的意思是从 Evaluation Stack 中取出两个值此二值皆为 Reference Type下面的值当作第一个参数上面的值当作第二个参数呼叫 mscorlibdll 所提供的 SystemStringConcat() method 来将此二参数进行字串接合(String Concatenation)将接合出来的新字串放在 Managed Heap将其 Reference 放进 Evaluation Stack值得注意的是由于 SystemStringConcat() 是 static method所以此处使用的指令是 call而非 callvirt(呼叫虚拟)执行完此道指令之后记忆体的变化如图 所示 图 请注意此时 Managed Heap 中的 Int() 以及 String("i+j+k=") 已经不再被参考到所以变成垃圾等待 GC 的回收 接着要执行第十七道指令 call void [mscorlib] SystemConsole::WriteLine(string)此指令的意思是从 Evaluation Stack 中取出一个值此值为 Reference Type将此值当作参数呼叫 mscorlibdll 所提供的 SystemConsoleWriteLine() method 来将此字串显示在 Console 视窗上SystemConsoleWriteLine() 也是 static method执行完此道指令之后记忆体的变化如图 所示 图 接着要执行第十八道指令 ret此指令的意思是结束此次呼叫(也就是 Main 的呼叫)此时会检查 Evaluation Stack 内剩下的资料由于 Main() 宣告不需要传出值(void)所以 Evaluation Stack 内必须是空的本范例符合这样的情况所以此时可以顺利结束此次呼叫而 Main 的呼叫一结束程式也随之结束执行完此道指令之后(且在程式结束前)记忆体的变化如图 所示 图 通过此范例读者应该可以对于 IL 有最基本的认识对 IL 感兴趣的读者应该自行阅读 Serge Lidin 所着的《Inside Microsoft NET IL Assembler》(Microsoft Press 出版)我认为熟知 IL 每道指令的作用是 NET 程式员必备的知识NET 程式员可以不会用 IL Assembly 写程式但是至少要看得懂 ILDASM 反编译出来的 IL 组合码 |