本文將介紹以下內(nèi)容:
- IL代碼分析方法
- Hello, world歷史
- .NET學習方法論
1. 引言
1988年Brian W. Kernighan和Dennis M. Ritchie合著了軟件史上的經(jīng)典巨著《The C programming Language》,,我推薦所有的程序人都有機會重溫這本歷史上的經(jīng)典之作。從那時起,,Hello, world示例就作為了幾乎所有實踐型程序設(shè)計書籍的開篇代碼,,一直延續(xù)至今,,除了表達對巨人與歷史的尊重,本文也以Hello, world示例作為我們扣開IL語言的起點,,開始我們循序漸進的IL認識之旅,。
2. 從Hello, world開始
首先,當然是展示我們的Hello, world代碼,,開始一段有益的分享,。
using System; using System.Data;
public class HelloWorld { public static void Main() { Console.WriteLine("Hello, world."); } }
這段代碼執(zhí)行了最簡單的過程,向陌生的世界打了一個招呼,,那么運行在高級語言背后真相又是什么呢,,下面開始我們基于上述示例的IL代碼分析。
3. IL體驗中心
對編譯后的可執(zhí)行文件HelloWorld.exe應(yīng)用ILDasm.exe反編譯工具,,還原HelloWorld的為文本MSIL編碼,,至于其工作原理我們期望在系列的后續(xù)文章中做以交代,我們查看其截圖為:
由上圖可知,,編譯后的IL結(jié)構(gòu)中,,包含了MANIFEST和HelloWorld類,其中MANIFEST是個附加信息列表,,主要包含了程序集的一些屬性,,例如程序集名稱、版本號,、哈希算法、程序集模塊等,,以及對外部引用程序集的引用項,;而HelloWorld類則是我們下面介紹的主角。
3.1 MANIFEST清單分析
打開MANIFEST清單,,我們可以看到
從這段IL代碼中,,我們的分析如下:
- .assembly指令用于定義編譯目標或者加載外部庫。在IL清單中可見,,.assembly extern mscorlib表示外部加載了外部核心庫mscorlib,,而.assembly HelloWorld則表示了定義的編譯目標。值得注意的是,,.assembly將只顯示程序中實際應(yīng)用到的程序集列表,,而對于加入using引用的程序集,如果并未在程序中引用,,則編譯器會忽略多加載的程序集,,例如System.Data將被忽略,,這樣就有效避免了過度加載引起的代碼膨脹。
- 我們知道m(xù)scorlib.dll程序集定義managed code依賴的核心數(shù)據(jù)類型,,屬于必須加載項,。 例如接下來要分析的.ctor指令表示構(gòu)造函數(shù),從代碼中我們知道沒有為HelloWord類提供任何顯示的構(gòu)造函數(shù),,因此可以肯定其繼承自基類System.Object,,而這個System.Object就包含在mscorlib程序集中。
- 在外部指令中還會指明了引用版本(.ver),;應(yīng)用程序?qū)嶋H公鑰標記(.publickeytoken),,公鑰Token是SHA1哈希碼的低8位字節(jié)的反序(如下圖所示),用于唯一的確定程序集,;還包括其他信息如語言文化等,。
- HelloWorld程序集中包括了.hash algorithm指令,表示實現(xiàn)安全性所使用的哈希算法,,系統(tǒng)缺省為0x00008004,,表明為SHA1算法;.ver則表示了HelloWorld程序集的版本號,;
- 程序集由模塊組成,, .module為程序集指令,表明定義的模塊的元數(shù)據(jù),,以指定當前模塊,。
- 其他的指令還有:imagebase為影像基地址;.file alignment為文件對齊數(shù)值,;.subsystem為連接系統(tǒng)類型,,0x0003表示從控制臺運行;.corflags為設(shè)置運行庫頭文件標志,,默認為1,;這些指令不是我們研究的重點,詳細的信息請參考MSDN相關(guān)信息,。
3.2 HelloWorld類分析
首先是HelloWorld類,,代碼為:
.class public auto ansi beforefieldinit HelloWorld extends [mscorlib]System.Object { } // end of class HelloWorld
- .class表明了HelloWorld是一個public類,該類繼承自外部程序集mscorlib的System.Object類,。
- public為訪問控制權(quán)限,,這點很容易理解。
- auto表明程序加載時內(nèi)存的布局是由CLR決定的,,而不是程序本身
- ansi屬性則為了在沒有被管理和被管理代碼間實現(xiàn)無縫轉(zhuǎn)換,。沒有被管理的代碼,指的是沒有運行在CLR運行庫之上的代碼,例如原來的C,,C++代碼等,。
- beforefieldinit屬性為HelloWorld提供了一個附加信息,用于標記運行庫可以在任何時候執(zhí)行類型構(gòu)造函數(shù)方法,,只要該方法在第一次訪問其靜態(tài)字段之前執(zhí)行即可,。如果沒有beforefieldinit則運行庫必須在某個精確時間執(zhí)行類型構(gòu)造函數(shù)方法,從而影響性能優(yōu)化,,詳細的情況可以參與MSDN相關(guān)內(nèi)容,。
然后是.ctor方法,代碼為:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代碼大小 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method HelloWorld::.ctor
- cil managed 說明方法體中為IL代碼,,指示編譯器編譯為托管代碼,。
- .maxstack表明執(zhí)行構(gòu)造函數(shù).ctor期間的評估堆棧(Evaluation Stack)可容納數(shù)據(jù)項的最大個數(shù)。關(guān)于評估堆棧,,其用于保存方法所需變量的值,,并在方法執(zhí)行結(jié)束時清空,或者存儲一個返回值,。
- IL_0000,,是一個標記代碼行開頭,一般來說,,IL_之前的部分為變量的聲明和初始化,。
- ldarg.0 表示裝載第一個成員參數(shù),在實例方法中指的是當前實例的引用,,該引用將用于在基類構(gòu)造函數(shù)中調(diào)用,。
- call指令一般用于調(diào)用靜態(tài)方法,因為靜態(tài)方法是在編譯期指定的,,而在此調(diào)用的是構(gòu)造函數(shù).ctor()也是在編譯期指定的,;而另一個指令callvirt則表示調(diào)用實例方法,它的調(diào)用過程有異于call,,函數(shù)的調(diào)用是在運行時確定的,,首先會檢查被調(diào)用函數(shù)是否為虛函數(shù),如果不是就直接調(diào)用,,如果是則向下檢查子類是否有重寫,如果有就調(diào)用重寫實現(xiàn),,如果沒有還調(diào)用原來的函數(shù),,依次類推直到找到最新的重寫實現(xiàn)。
- ret表示執(zhí)行完畢,,返回,。
最后是Main方法,代碼為:
.method public hidebysig static void Main() cil managed { .entrypoint // 代碼大小 11 (0xb) .maxstack 8 IL_0000: ldstr "Hello, world." IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method HelloWorld::Main
- .entrypoint指令表明了CLR加載程序HelloWorld.exe時,,是首先從.entrypoint方法開始執(zhí)行的,,也就是表明Main方法將作為程序的入口函數(shù),。每個托管程序必須有并且只有一個入口點。這區(qū)別于將Main函數(shù)作為程序入口標志,。
- ldstr指令表示將字符串壓棧,,"Hello, world."字符串將被移到stack頂部。CLR通過從元數(shù)據(jù)表中獲得文字常量來構(gòu)造string對象,,值得注意的是,,在此構(gòu)造string對象并未出現(xiàn)在《第五回:深入淺出關(guān)鍵字---把new說透》中提到的newobj指令,對于這一點的解釋我們將在下一回中做簡要分析,。
- hidebysig屬性用于表示如果當前類作為父類時,,類中的方法不會被子類繼承,因此HelloWorld子類中不會看到Main方法,。
接下來的一點補充:
- 關(guān)于注釋,,IL代碼中的注釋和C#等高級語言的注釋相同,其實編譯器在編譯IL代碼時已經(jīng)將所有的注釋去掉,,所以任何對程序的注釋在IL代碼中是看不見的,。
3.3 回歸簡潔
去粗取精,我們的IL代碼可以簡化,,下面的代碼是基于上面的分析,并去處不重要的信息,,以更簡潔的方式來展現(xiàn)的HelloWorld版IL代碼,詳細的分析就以注釋來展開吧,。
4. 結(jié)論
結(jié)束本文,,我們從一個點的角度和IL來了一次接觸,,除了了解幾個重要的指令含義,,更重要的是已經(jīng)走進了IL的世界,。通過一站式的掃描HelloWorld的IL編碼,,我們還不足以從全局來了解IL,,不過第一次的親密接觸至少讓我們太陌生,,而且隨著系列文章的深入我們將逐漸建立起這種認知,,從而提高我們掌握了解.NET底層的有效工具,。本系列也將在后續(xù)的文章中,,逐漸建立起這種使用工具的方法,,敬請關(guān)注,。
|