Head First IL中間語言之 實例深入經典詮釋
這里,就再寫一篇博文,用一個應用程序的實例來深入的說明一番CLR環境下的IL語言的語法,基本運行機制和原理.
首先,找一段IL語言的例子,從codeproject找了一段IL程序修改了下
.method static void main() cil managed
{
.maxstack 2
.entrypoint
.locals init (int32, temp)
newobj instance void Donis.CSharpBook.ZClass::.ctor()
dup
call instance int32 Donis.CSharpBook.ZClass::AddFields()
stloc.0
ldstr "the total is {0}"
ldloc.0
box int32
call void [mscorlib] System.Console::WriteLine(string,object)
call instance int32 Donis.CSharpBook.ZClass::SubtractFields()
stloc.0
ldstr "The difference is {0}"
ldloc.0
box int32
call void [mscorlib] System.Console::WriteLine(string,object)
}
這段程序,深究的程度不同,值得琢磨的地方也就越多;下面,我來一點一點的分析:
我們跳過定義和開頭,從這一句開始:
newobj instance void Donis.CSharpBook.ZClass::.ctor()
這里,使用了一個newobj指令.這個指令用來分配生成一個未初始化的對象或者是值類型,并且call ctor()構造函數.
這里,有的朋友會郁悶,為什么這個指令可以分配一個value type值類型.這的確是這個指令本分的功能而且是比較特別的地方.
ECMA標準文檔對這個指令的描述如下:(請允許我引用一段E文那,呵呵,E文不好的詞霸調出來吧):
--------------------------Reference--------------------------------
The newobj instruction creates a new object or a new instance of a value type. ctor is a metadata token that indicates the name, class, and signature of the constructor to call. If a constructor exactly matching the indicated name, class and signature cannot be found, MissingMethodException is thrown.
The newobj instruction allocates a new instance of the class associated with ctor and initializes all the fields in the new instance to 0 (of the proper type) or null as appropriate. It then calls the constructor with the given arguments along with the newly created instance. After the constructor has been called, the now initialized object reference is pushed on the stack.
From the constructor’s point of view, the uninitialized object is argument 0 and the other arguments passed to newobj follow in order.Value types are not usually created using newobj. They are usually allocated either as arguments or local variables, using newarr (for zero-based, one-dimensional arrays), or as fields of objects. Once allocated, they are initialized using initobj. However, the newobj instruction can be used to create a new instance of a value type on the stack, that can then be passed as an argument, stored in a local, etc.
--------------------------Reference--------------------------------
這里,大家最好務必讀一下,有助于下面的理解.
同時,特別提醒大家注意這一句:"After the constructor has been called, the now initialized object reference is pushed on the stack."
這一句,有助于下面大家dup指令的理解,IL編譯器的指令優化和多方法調用的理解.
OK,接下來就是dup指令.ECMA的CLR文檔的part 3對這個指令的解釋很簡單,只有一句:Duplicate the value on the top of the stack.實際上用法也的確很簡單.
復雜的一部分,是中間語言Complier的一些指令優化拐了一點點小彎.
接下來,我們來說明這一句:
call instance int32 Donis.CSharpBook.ZClass::AddFields()
不好意思哈,又要引用一段E文:
--------------------------Reference--------------------------------
The call instruction calls the method indicated by the descriptor method. method is a metadata token (a methodref, methoddef, or methodspec) that indicates the method to call, and the number, type, and order of the arguments that have been placed on the stack to be passed to that method, as well as the calling convention to be used. The call instruction can be immediately preceded by a tail. prefix to specify that the current method state should be released before transferring control .
--------------------------Reference--------------------------------
上面說的有必要再補充下,Call方法再執行的時候,計算堆棧,注意,這里說的是計算堆棧stack,不是托管堆heap,
從棧頂開始,首先要放入instance的referece,也就是ZClass在托管堆里面的引用,然后依次放入需要傳入到AddFields方法里面的參數.我們的例子中,不傳遞參數.
then:
stloc.0
ldstr "the total is {0}"
ldloc.0
第一句指令,把剛才call那句執行完畢以后的一個返回的類型為int32的變量pop到local變量集合的第一個中去.然后load一個字符串,最后,把local變量里面的第一個,也就是temp變量的值stack的最上面.
這里需要特別提醒一下的是:ldstr這句中,字符串是保存在托管堆中的.load到stack中的,只是heap的一個引用.也就是說,這里只是一個引用類型.理解這點,有助于下面指令的理解.
接下來,該是本文中精彩的部分到了:
這里兩條指令一起介紹:
box int32
call void [mscorlib] System.Console::WriteLine(string,object)
box指令用來執行裝箱操作,他防止了指令異常的發生.它從計算堆棧中復制一個值類型實例的所有的字段,然后在托管堆上面創建一個對值類型進行裝箱的對象,并且將新建對象的引用放回到計算堆棧當中去.此時,原來的值類型就被一個引用類型代替了.unbox指令執行相反的拆箱操作.
為什么要用box指令呢?有人可能會問道這個.
這里,是因為下面的WriteLine方法調用了兩個引用類型的參數,所以,需要把int32類型的一個值類型轉換成為一個Object的引用類型.
注意:這里返回類型是void,讀者們可以思考一下,此時的stack的頂部,是什么呢??
最后是這條指令特別介紹一下:
call instance int32 Donis.CSharpBook.ZClass::SubtractFields()
這個指令前面已經詳細介紹過一次,這里再次討論一下.
這里可能有人會問道,call指令需要在stack的頂部放置ZClass的reference啊,這里為什么沒有用dup或者別的指令啊.
啊哈,這里是因為在上面的指令中,newobj將ZClass的reference放置到了stack中,dup又把這個值復制了一次從新push到了stack中,這里stack中就有兩個引用啦.
這也涉及到IL編譯器的編譯優化問題的相關的研究,這里就不深入討論這個話題了.
值得一提的是,我在寫這篇文章之前,看到相當多類似話題的討論中,很多人對dup指令的用法感到很困惑,怎么有的時候要用,有的時候有不用呢?怎么前面用了后面又不用呢?然后各種猜想假設就接踵而來.........
這里,和newobj指令和call指令的使用結合起來解釋這個問題就容易了.
恩,不羅嗦了,over吧.敲累了.后續更多精彩內容奉上 ^_^
posted on 2007-10-19 23:28 lbq1221119 閱讀(3327) 評論(18) 收藏 舉報
浙公網安備 33010602011771號