对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道你的代码是如何运行相互调用的,此言一出不明觉厉。 然后开始接触IL,了解了一段时后才发现原来读懂IL代码并不难。进入正题 1.1 什么是IL IL是.NET框架中中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码(来源百度) 1.2 为什么要了解IL 在很多时候不明白代码是如何操作时就可以通过IL指令来解释,比如,装箱,拆箱是否只是听别人说或者书上讲是怎么怎么实现的,自己是否证实过呢?了解IL指令你可清楚看到是每一步是如何处理的 1.3 怎么学IL 世上有个定律叫“二八定律” ,80%的功能,只要用20%的技术就可以完成,但要完成另外20%可能就需要80%技术了,对于IL代码也是如此,有200多个指令,我们只需要用到其20%的指令就可以解决我们80%的问题了,所以我不会写太多,只是让大家能看懂普通的程序代码编译成IL代码后就行了,还有就是要多看,IL代码的每一条指令都是特定的意思,看得多了自然就懂了,当对自己代码有疑问时尝试看看它对应的IL代码,也许你会了解得更多。 IL指令大全 点这里 IL代码编译器 ILDasm 点这里 二 如何查看IL代码2.1 步骤 1 编写代码并编译通过 2 找到源文件的obj文件下的 .exe文件 3 导入到ILDasm中反编译成IL代码 上图 1 -2步 3 导入到ILDasm中
ILDasm中图标含义
三 如何读IL(大致了解)以上步骤完成后我们就可以看到代码被编译后的IL代码,以下部份将会对每一条IL指令做详细的解释 C#代码 1 static void Main(string[] args) 2 { 3 int i = 1; 4 int j = 2; 5 int k = 3; 6 Console.WriteLine(i+j+k); 7 }
IL代码 // Call Stack 是一个局部变量列表,用于存储 .locals init (int32 V_0,int32 V_1,int32 V_2)初始化后的参数 V_0,V_1,V_2 // Evaluation Stack 是一个栈 ldc.i4.2 这种指令都会先把值压入栈中等待操作 在第四段时大家可以理解得更清楚一点 1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint //程序入口 4 // Code size 19 (0x13) 5 .maxstack 3 //定义函数代码所用堆栈的最大深度,也可理解为Call Stack的变量个数 11 IL_0003: ldc.i4.2 //加载第二个变量"j" (压入Evaluation Stack中) 12 IL_0004: stloc.1 //把"j"赋值给Call Stack中第1个位置(V_1) 13 IL_0005: ldc.i4.3 //加载第三个变量"k" (压入Evaluation Stack中) 14 IL_0006: stloc.2 //把 "k" 赋值给Call Stack中第2个位置(V_2) 15 16 //上面代码初始化完成后要开始输出了,所要把数据从Call Stack中取出 17 18 IL_0007: ldloc.0 //取Call Stack中位置为0的元素(V_0)的值("i"的值) (相当于Copy一份值Call Stack中V_0的值。V_0本身的值是不变的) 19 IL_0008: ldloc.1 //取Call Stack中位置为1的元素(V_1)的值("j"的值) (同上) 20 IL_0009: add // 做加法操作 21 IL_000a: ldloc.2 // 取出Call Stack中位置为2的元素(V_2)的值("k"的值) 22 IL_000b: add // 做加法操作 23 IL_000c: call void [mscorlib]System.Console::WriteLine(int32) //调用输出方法 24 IL_0011: nop 25 IL_0012: ret //即为 return 标记 返回值 26 } // end of method Program::Main 指令详解 .maxstack:代码中变量需要在Call Stack 中占用几个位置 .locals init (int32 V_0,int32 V_1,int32 V_2):定义变量并存入Call Stack中 nop:即No Operation 没有任何操作,我们也不用管它, ldstr.:即Load String 把字符串加压入Evaluation Stack中 stloc.:把Evaluation Stack中的值弹出赋值到Call Stack中 ldloc.:把Call Stack中指定位置的值取出(copy)存入 Evaluation Stack中 以上两条指令为相互的操作stloc赋值,ldloc取值 call: 调用指定的方法 ret: 即return 标记返回 每一句IL代码都加了注释后,是不是觉得IL代码其实并不难呢,因为它的每一条指令都是固定的,你只要记住了,看IL代码就比较轻松了。
四 如何读IL(深入了解)4.1 提出问题 有了上面的一点IL基础后,现在我们来深入一点点, 有如下几个问题: 1 当 ldc.i4.1 这一指定加载 “i” 这个变量后并没有马上赋值给Call Stack中的元素,而是要执行 stloc.0 后才赋值,那没赋值前是存在哪里的呢? 2 ldloc.0 把元素取出来后,存在哪里的? 3 add操作完成后值存在哪里? 4.2 概念引入 Managed Heap::這是動態配置(Dynamic Allocation)的記憶體,由 Garbage Collector(GC)在執行時自動管理,整個 Process 共用一個 Managed Heap(我理解为托管堆,存储引用类型的值)。 Evaluation Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Evaluation Stack(我理解为类似一个临时存放值类型数据的线程栈) Call Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有自己專屬的 Call Stack。每呼叫一次 method,就會使得 Call Stack 上多了一個 Record Frame;呼叫完畢之後,此 Record Frame 會被丟棄(我理解为一个局部变量表,用于存放.locals init(int32 V_0)指令的参数值如:V_0)
4.3 IL指令详解 对三个名词做解释后现在我们再来仔细看看执行IL指令时,对应的变量是如何存放的 IL_0001: ldc.i4.1 //加载第一个变量i 当为-1 时IL指令为ldc.i4.M1,当超过8时就是一个统一指令 ldc.i4.S 当执行这一条指令时会把变量i 的值压入Evaluation Stack中做临时存储 IL_0002: stloc.0 //把i 赋值给Call Stack中第0个位置 IL_0007: ldloc.0 //取出Call Stack位置为0的元素 (i) IL_000b: add // 做加法操作 五 总结
|