在上一篇.NET CIL第一篇:CIL介紹和入門中我們簡要介紹了CIL編程的本質,和學習CIL編程的價值。還介紹了CIL的指令、特性和操作碼。接下來的文字中會頻繁出現:指令、特性、操作碼這些關鍵字。所以請確定你已經知道它們的含義了。
入棧和出棧:CIL基於棧的本質
像C#這樣的高級.NET語言,總是試圖盡量隱藏底層的實現。.NET開發一個不太為人注意的方面就是CIL實際上是一個完全以棧為基礎的開發語言。回憶我們熟悉的System.Collections命名空間中的Stack類型的功能,它被用於壓一個值入棧,同時也能將棧頂的值彈出以供使用。當然,CIL開發人員不是使用System.Collection.Stack來實現入棧和出棧的,不過從思路上是相通的。
正式地說,在CIL中用來負責這個棧實現的部分叫做虛擬執行棧(virtual execution stack)。從下面的介紹中,你將看到CIL提供了一系列操作碼來完成壓入值到這個棧中,這個過程的術語叫加載(load)。同時,CIL也定義了一系列操作碼來將棧頂的值移到內存中(例如局部變量),這個過程的術語叫存儲(store)。
CIL不允許直接訪問一個數據,包括局部變量、方法中傳入的變量或者類型的字段數據。為了實現訪問,必須顯示地加載數據到棧中,並在使用時彈出。請留心這一點,因為這能夠解釋為什麼CIL代碼看起來有些冗余。
下面通過一段簡單的沒有參數和返回值的函數PrintMessage(),來理解CIL是如何應用棧這個模型的。在實現這個方法時,我們只將一個局部的字符串變量輸出到標准的輸出流。
public void PrintMessage()
{
string myMessage = "Hello";
Console.WriteLine(myMessage);
}
如果你觀察過C#編譯器是如何將這個方法翻譯成CIL的,將會首先看到PrintMessage()方法用.locals指令為局部的字符串定義了一個存儲空間。接著,這個局部的字符串通過ldstr和stloc.0被加載和存儲到這個局部變量中。(stloc.0可以這樣理解:存儲當前的值到索引為0的局部變量。)
這個索引0處的值接著通過ldloc.0(加載索引為0的局部參數)被夾在到內存來供System.Console.WriteLine()方法使用(通過call操作碼指定)。最後,這個方法以ret操作碼結束返回,以下是PrintMessage()方法的CIL代碼:
.method public hidebysig instance void PrintMessage() cil managed
{
// Code size 15 (0xf)
.maxstack 1
//定義一個局部的字符串變量(在索引0處)。
.locals init ([0] string myMessage)
IL_0000: nop
//加載一個值為"Hello"的字符串。
IL_0001: ldstr "Hello"
//存儲字符串的值到棧上的局部變量。
IL_0006: stloc.0
//調用索引0處的值。
IL_0007: ldloc.0
//使用當前的值調用方法。
IL_0008: call void [mscorlib]System.Console::WriteLine(string)
IL_000d: nop
IL_000e: ret
} // end of method HelloProgram::PrintMessage
說明:正如我們所看到的,CIL支持使用雙斜槓的注釋語法(也支持/*…*/這個語法)。同C#一樣,注釋將被編譯器完全忽略。
今天的內容比較少,但是比較抽象。今天反復出現:指令、特性、操作碼等這些概念。所以請確保您知道它們的含義,或者查看.NET CIL第一篇:CIL介紹和入門來回憶一下。本來打算發兩篇,因為這篇實在是太短了,今天由於同學來看我,所以下一篇放在凌晨吧。至此我們對CIL基礎知識的介紹告一段落,下一篇將來研究CIL編程的實際使用。千萬不要錯過啊!