前言
IL是什么?
Intermediate Language (IL)微软中间语言
C#代码编译过程?
如下图
LC编译器:无论是VB code还是C# code都会被Language Compiler转换为MSIL(元数据和中间语言指令);
JIT编译器:根据系统环境将MSIL中间语言指令转换为机器码
为什么要了解IL代码?
如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以通过IL代码透过表面看本质;
IL也是更好理解、认识CLR的基础;
大量的实例分析是以IL为基础的,所以了解IL,是读懂他人代码的必备基础,同时自己也可以获得潜移默化的提高;
一、如何把ILDasm导入到VS中
首先找到ildasm.exe(我的路径):
在VS,工具—外部工具,添加一个:
效果:
符号及其含义:
二、分析IL代码
1.部分概念
Managed Heap(托管堆):这就是NET中的托管堆,用来存放引用类型,它是由GC(垃圾回收器自动进行回收)管理;
Call Stack(调用堆栈):调用堆栈:调用堆栈是一个方法列表,按调用顺序保存所有在运行期被调用的方法。
Evaluation Stack(计算堆栈):每个线程都有自己的线程栈,IL 里面的任何计算,都发生在 Evaluation Stack 上,其实就是一个 Stack 结构。可以 Push,也可以 Pop。(计算完后就只剩就算后的数据。)
Record Frame(局部变量表)
2.VS代码
代码如下(示例):
using System;
namespace 字符串转换
{
class Program
{
static void Main(string[] args)
{
string test = "hello word";
char[] c = test.ToCharArray();
byte[] b = new byte[c.Length];
for (int i = 0; i < c.Length; i++)
{
b[i] = Convert.ToByte(c[i]);
Console.WriteLine("{0:X} {1}", b[i],c[i]);
}
Console.ReadKey();
}
}
}
3.IL指令
代码如下(示例):
内部存在循环,注释以第一个循环的备注
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 90 (0x5a)
.maxstack 4
.locals init ([0] string test,
[1] char[] c,
[2] uint8[] b,
[3] int32 i,
[4] bool V_4)//定义 参数 test,c,b,i,V_4(此时已经把他们存入了Call Stack中的Record Frame中)
IL_0000: nop //占位符
IL_0001: ldstr "hello word" //把字符串"hello word"压入计算栈中
IL_0006: stloc.0 //从计算栈的顶部弹出"hello word"并将其存储到索引 0 处的局部变量列表中V_0位置
IL_0007: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
IL_0008: callvirt instance char[] [mscorlib]System.String::ToCharArray()//调用转成数组方法
IL_000d: stloc.1 //从计算栈的顶部弹出数组并将其存储到索引 1处的局部变量列表中V_1位置
IL_000e: ldloc.1 //将索引 1 处的局部变量数组加载到计算堆栈上
IL_000f: ldlen //将从零开始数组的元素的数目推送到计算堆栈上
IL_0010: conv.i4 //将位于计算堆栈顶部的值(数组的元素的数目)转换为 int32
IL_0011: newarr [mscorlib]System.Byte //将对新的维数组(Byte)的对象引用推送到计算堆栈上
IL_0016: stloc.2 //从计算栈的顶部弹出新数组并将其存储到索引 2处的局部变量列表中V_2位置
IL_0017: ldc.i4.0 //将整数值 0 作为 int32 推送到计算堆栈上
IL_0018: stloc.3 //从计算栈的顶部弹出0并将其存储到索引 3处的局部变量列表中V_3位置
IL_0019: br.s IL_0047 //跳转到指令IL_0047
IL_001b: nop
IL_001c: ldloc.2 //将局部变量(Byte数组)加载到计算堆栈上
IL_001d: ldloc.3 //将局部变量0加载到计算堆栈上
IL_001e: ldloc.1 //将局部变量(字符数组)加载到计算堆栈上
IL_001f: ldloc.3 //将局部变量0加载到计算堆栈上
IL_0020: ldelem.u2 //将字符数组[0]处加载到计算堆栈上
IL_0021: call uint8 [mscorlib]System.Convert::ToByte(char)//对字符数组[0]调用ToByte函数
IL_0026: stelem.i1 //用转换后的byte值替换给定索引处的数组元素。
IL_0027: ldstr "{0:X} {1}" //把字符串"{0:X} {1}"压入计算栈中
IL_002c: ldloc.2 //将局部变量(Byte数组)加载到计算堆栈上(当前Byte数组[0]已更新)
IL_002d: ldloc.3 //将局部变量0加载到计算堆栈上
IL_002e: ldelem.u1 //将Byte数组[0]处加载到计算堆栈上
IL_002f: box [mscorlib]System.Byte //转换为引用类型
IL_0034: ldloc.1 //将局部变量(字符数组)加载到计算堆栈上
IL_0035: ldloc.3 //将局部变量0加载到计算堆栈上
IL_0036: ldelem.u2 //将字符数组[0]处加载到计算堆栈上
IL_0037: box [mscorlib]System.Char //转换为引用类型,数据存放入托管堆,计算栈上存放数据的引用。
IL_003c: call void [mscorlib]System.Console::WriteLine(string,//调用WriteLine(string, object,object)
object,//根据计算栈中的数据为:
object)//"{0:X} {1}" , Byte数组[0]引用, 字符数组[0]引用
IL_0041: nop
IL_0042: nop
IL_0043: ldloc.3 //将局部变量0加载到计算堆栈上
IL_0044: ldc.i4.1 //将整数1加载到计算栈上
IL_0045: add //相加(此处为循环时的++)
IL_0046: stloc.3 //弹出相加得到的1并压入局部变量V-3处。(替换了原来的0,一个循环结束)
IL_0047: ldloc.3 //将索引 3 处的局部变量加载到计算堆栈上
IL_0048: ldloc.1 //将索引 1 处的局部变量数组加载到计算堆栈上
IL_0049: ldlen //将数组的元素的数目推送到计算堆栈上
IL_004a: conv.i4 //将位于计算堆栈顶部的值数组的元素的数目转换为 int32。
IL_004b: clt //比较索引 3 处的局部变量与索引 1 处的局部变量数组元素个数,如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。
IL_004d: stloc.s V_4 //从计算堆栈的顶部弹出当前值(判断值)并将其存储在局部变量列表中的 V_4 处(Init处定义的变量)。
IL_004f: ldloc.s V_4 //将V_4加载到计算堆栈上。
IL_0051: brtrue.s IL_001b //如果 value 为 true、非空或非零,则跳转到IL_001b
IL_0053: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()//ReadKey函数调用
IL_0058: pop //移除当前位于计算堆栈顶部的值
IL_0059: ret //当前方法返回
} // end of method Program::Main
4.结果截图
参考链接:
https://www.cnblogs.com/yinrq/p/5486103.html
https://docs.microsoft.com/zh-tw/previous-versions/dd229210(v=msdn.10)?redirectedfrom=MSDN
https://www.cnblogs.com/zery/p/3366175.html