上一篇提到了最基本的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代碼解析。