聲明:
本文譯自 Kenny Kerr 的 blog,如果大家有更好的 MSIL 入門文章也歡迎推薦,謝謝。
在本文的第二部分裡,我將探討一下局部變數的使用。為了展示這個過程,讓我們來寫一個將兩數相加的簡單程式。
在 MSIL 方法中,使用 .locals 指示符來聲明局部變數
.local init( int32 first,
int32 second,
int32 result )
該語句為當前方法聲明了三個局部變數。在本例中,恰巧都是 int32 類型—— System.Int32 類型的同義字。init 指定這些變數需要以其對應類型的預設值進行初始化。變數名稱也可以被省略,那樣的話你需要通過聲明時的zero-based 索引來指明變數。當然,使用變數名會增加代碼的可讀性。
在我們繼續之前,需要先明確一下 MSIL 當中顯式使用棧的方式。當你需要向一個指令傳值的時候,首先需要把那些值壓棧。而指令需要進行彈棧操作以讀出數值。類似地,在調用方法時也需要將對象引用 ( 如果有的話 ) 和需要傳遞的參數按順序壓棧。在開始調用方法時,所有的參數以及對象引用會被彈棧。使用 ldloc 指令來將變數的值壓棧;使用 stloc 指令將棧頂的值彈出並儲存到指定變數中。另外要時刻記得,實值型別的右值會被直接儲存到棧中,而對象 ( 參考型別的執行個體 ) 不會,因為 CLI 不允許在棧上為參考型別的對象分配記憶體,而只是將對象的引用儲存到棧上。這類似於將一個原生C++對象分配在堆上並將一個指向它的指標儲存在棧中。請在閱讀本文的時候記住這個棧的存在,這將有助於理解為什麼數值會不停地在棧上壓入彈出。
下一步是要讓使用者輸入相加的數值。
ldstr "First number: "
call void [mscorlib]System.Console::Write(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
stloc first
上一篇文章中提到, ldstr 指令將字串壓棧,而 call 指令調用 Write 方法,並將其參數彈棧。下一個 call 指令調用 ReadLine 方法,該方法從控制台讀入並返回的字串被壓棧。因為傳回值正好位於棧頂,所以我們直接調用 Int32::Parse 方法,將讀入的字串彈棧並將其等價的 int32 類型的數值壓棧。注意為了清楚起見,我省略了所有的錯誤處理。接下來用 stloc 指令將數值彈棧並儲存於局部變數 first 當中。第二個數值以同樣的方式獲得,並儲存於變數 second 當中。
接下來,我們使用 add 指令完成加法並將結果儲存到變數 result 。
ldloc first
ldloc second
add
stloc result
最後顯示結果。
ldstr "{0} + {1} = {2}"
ldloc first
box int32
ldloc second
box int32
ldloc result
box int32
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
我們使用WriteLine 的一個重載版本,它接收一個格式化字串和三個 object 對象作為參數。調用之前,每一個參數都必須按順序壓棧。因為數值是以 int32 這個實值型別來儲存的,所以我們需要對其進行裝箱操作,否則將無法匹配方法簽名。 ldloc 指令將每一個參數壓棧,隨後 box 指令被應用於每個參數。裝箱操作將數值彈棧,而後構造一個包含該數值拷貝的新對象,並將新對象的引用壓棧。
完整地程式如下:
.method static void main()
{
.entrypoint
.maxstack 4
.locals init (int32 first,
int32 second,
int32 result)
ldstr "First number: "
call void [mscorlib]System.Console::Write(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
stloc first
ldstr "Second number: "
call void [mscorlib]System.Console::Write(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
stloc second
ldloc first
ldloc second
add
stloc result
ldstr "{0} + {1} = {2}"
ldloc first
box int32
ldloc second
box int32
ldloc result
box int32
call void [mscorlib]System.Console::WriteLine(string, object, object, object)???
ret
}
最後值得注意的是,本例中的程式需要大小為4的棧空間,因為最後調用 WriteLine 方法時需要傳入4個參數。