上一篇提到了最基本的IL代码,应该是比较通俗易懂的,所以有了上一篇的基础之后,这篇便要深入一点点的来讲述了。
首先我必须再来说一些重要的概念:
Evaluation Stack(评估栈):这是由.NET CLR在执行时候自动管理的记忆体,每一个线程都有自己的评估栈,也就是说,它是用来存储临时变量的线程栈(应该可以这么理解)。值类型存储数据,引用类型存储地址。
Call Stack(调用栈):这也是由.NET CLR在执行时候自动管理的记忆体,每一个线程都有自己的调用栈,每一次调用method,就会产生一个栈帧(stack frame),当方法结束的时候,此栈帧就会被丢弃。
大家一定疑问怎么都是栈呀,堆到哪里去了嘞,大家别急,下面说:
Managed Heap:这是动态配置的记忆体,由Garbage Collector(GC)执行时自动管理,整个程序共用一个。我把这个理解为托管堆,用来存储引用类型的值。
这一篇主要是以引用类型为主来讲述IL代码。
大家都知道把值类型变成引用类型是采用装箱的操作,所以这里也先说一下装箱的过程:
(1)内存分配,在Managed Heap(托管堆)中分配一个内存空间;
(2)将值类型的字段拷贝到分配的内存中;
(3)将托管堆中的对象地址返回给新的对象,Over。
以上是一些基本知识点,现在主要来讲解IL代码:
正规代码如下:
我用Reflector进行查看IL代码:
上图就是IL代码了,经过上一篇的介绍,我觉得这里大家应该也看得懂吧,下面我就来解释这段IL代码吧。
.method private hidebysig static void Main(string[] args)cilmanaged { .entrypoint //入口 .maxstack 2 //评估栈可容纳数据项的最大个数。 .locals init ( [0] string str, [1] int32 num) //定义并初始化参数,并存入局部变量表(Call Stack)中。 L_0000: nop //No Operation L_0001: ldstr "Helius" //将字符串“Helius”压入评估栈中。 L_0006: stloc.0 //将字符串从评估栈中弹出,赋值给局部变量表的第0个变量。 L_0007: ldc.i4.s 0x1b //解析:int类型的数值大小不一样,IL代码也是不一样的。比如int i=-1;IL代码为ldc.i4.M1;当i的值大于等于9的时候,IL代码就为ldc.i4.s(i的十六进制表示形式),当i的值小于等于-2时,IL代码表示为ldc.i4.s(i)。 L_0009: stloc.1 //将值从评估栈中弹出,赋值给局部变量表的第1个变量。 L_000a: ldloca.s num //取出局部变量表中num的值“27”并压入评估栈中。 L_000c: call instance string [mscorlib]System.Int32::ToString() //从评估栈中取出数值“27”,调用ToString方法转成string类型并将引用存入评估栈中。 L_0011: ldloc.0 //取出局部变量表中的第0个位置元素值“Helius”,并压入评估栈,(此时评估栈中有两个值,字符串“Helius”和“27”的引用地址)。 L_0012: call string [mscorlib]System.String::Concat(string, string) //调用String类的Concat方法把字符串拼接并存入托管堆中,把引用地址返回给评估栈。 L_0017: call void [mscorlib]System.Console::WriteLine(string) //调用输出方法,调用输出方法后评估栈中的值(指向托管堆中字符的地址)会被回收。 L_001c: nop ------------------------------------------分割线--------------------------------------------------------------------------------------------------- L_001d: ldloc.1 //取局部变量表中的第1个位置参数num值,存入评估栈中。 L_001e: box int32 //把num值27装箱,并返回托管堆中的地址存入评估栈中。 L_0023: ldloc.0 //去局部变量表中的第0个参数,并压入评估栈中。 L_0024: call string [mscorlib]System.String::Concat(object, object)//弹出评估栈中两个值,并调用String的Concat方法把字符拼接,存入托管堆中,并返回引用地址到评估栈中。 L_0029: call void [mscorlib]System.Console::WriteLine(string) //调用输出方法。 L_002e: nop L_002f: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()//调用ReadKey方法 L_0034: pop //把评估栈的内容清空 L_0035: ret //return 标记返回 } 以图来解释上面的例子可能更明白一点(程序员学会画图很重要,我记得去面试的时候,面试官还叫我画项目的架构图):图1 图2 图3
从以上的图中可以明显看出调用方法与装箱的流程。引用类型都是存放在托管堆里面的,而栈只存放引用类型的地址,这点是要特别注意的。
下一篇我会写方法,委托和类的IL代码解析。
时间: 2024-10-13 00:20:20