.NET值類型變量“活”在哪個(gè)堆棧中?——MSIL學(xué)習(xí)筆記(一)金旭亮不管是什么語(yǔ)言編的.NET程序,最后都會(huì)被各自的編譯器編譯成MSIL。當(dāng)程序運(yùn)行時(shí),.NE" />

日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

.NET值類型變量“活”在哪?

系統(tǒng) 1792 0
<iframe align="top" marginwidth="0" marginheight="0" src="http://www.zealware.com/46860.html" frameborder="0" width="468" scrolling="no" height="60"></iframe>
.NET值類型變量“活”在哪個(gè)堆棧中?
——MSIL學(xué)習(xí)筆記(一)
金旭亮
不管是什么語(yǔ)言編的.NET程序,最后都會(huì)被各自的編譯器編譯成MSIL。當(dāng)程序運(yùn)行時(shí),.NET JIT編譯器從程序集中讀入IL指令并將其動(dòng)態(tài)編譯為可被本地CPU執(zhí)行的機(jī)器指令再執(zhí)行。
程序集中的IL代碼以二進(jìn)制方式存在,人閱讀起來(lái)相當(dāng)不便,正如傳統(tǒng)的Win32程序可以被反匯編成匯編程序,.NET程序集中的IL代碼也可以被反匯編成易于閱讀的IL匯編程序。如果您愿意的話,可以用任意一個(gè)文本編輯器直接撰寫IL匯編源代碼,然后使用ilasm.exe程序?qū)⑵渚幾g為包含二進(jìn)制形式的IL指令。CLR只能執(zhí)行二進(jìn)制的IL指令。
.NET SDK 的另一個(gè)工具ildasm.exe可以用于將一個(gè)程序集反匯編為IL程序,在學(xué)習(xí).NET時(shí),這個(gè)工具非常有用,可以展示出高級(jí)語(yǔ)言(如C#和VB.NET)編寫的程序是如何被CLR執(zhí)行的。
然而,相比C#和VB.NET的資料滿天飛,MSIL的技術(shù)資料少得可憐。我能夠查閱的只有MSDN中有關(guān)IL指令的文檔(還只是針對(duì)Reflection.Emit名字空間中的類的),以及一本由Serge Lidin著的《inside Microsoft .NET IL assembler》, Serge Lidin是匯編器ilasm.exe工具的主要開發(fā)者,因此,他的書應(yīng)具有相當(dāng)?shù)臋?quán)威性,然而,這位技術(shù)牛人的寫作水平實(shí)在不敢恭維,整本書象是一本參考手冊(cè)。此書國(guó)內(nèi)引進(jìn)了中文版,然而翻譯得很不好。幸運(yùn)的是其光盤中附上了英文原版,實(shí)乃國(guó)人之大幸。
IL 可以看成是一個(gè)“面向?qū)ο蟮膮R編語(yǔ)言”,它提供了許多指令直接對(duì)對(duì)象進(jìn)行操作,比如newobj指令創(chuàng)建對(duì)象,box指令進(jìn)行裝箱等。
IL 指令的一個(gè)最重要特性是它是基于堆棧的。幾乎每一條指令都要與堆棧打交道:或者向堆棧中Push一些數(shù)據(jù),或者從中Pop一些數(shù)據(jù)。
請(qǐng)看以下C#代碼段:
class Program
{
static void Main( string [] args)
{
int i = 100;
int j = 200;
int reslut = i + j;
}
}
C#編譯器將生成以下IL指令,其功能我在注釋中有詳細(xì)說(shuō)明:
.method private hidebysig static voidMain(string[] args) cil managed
{
.entrypoint
// 代碼大小 15 (0xf)
.maxstack2
.locals init ([0] int32 i,
[1] int32 j,
[2] int32 reslut)
IL_0000:nop
IL_0001:ldc.i4.s 100 //將100壓入堆棧
IL_0003:stloc.0 //從堆棧中彈出先前壓入的100,傳給局部變量 i
IL_0004:ldc.i4 0xc8 //將200壓入堆棧
IL_0009:stloc.1 //從堆棧中彈出先前壓入的200,傳給局部變量 j
IL_000a:ldloc.0 //將局部變量 i的值壓入堆棧
IL_000b:ldloc.1 //將局部變量 j的值壓入堆棧
IL_000c:add //連繼彈出兩個(gè)整數(shù),相加得300,又壓入堆棧
IL_000d:stloc.2 //從堆棧中彈出結(jié)果,保存到局部變量 reslut中
IL_000e:ret //返回指令
} // end of method Program::Main
可以看到,所有的指令都涉及到堆棧。
然而,我在研究IL匯編程序的時(shí)候,卻被“堆?!眱蓚€(gè)字弄糊涂了。
幾乎所有的C#書,都說(shuō)值類型變量是生存在堆棧中,當(dāng)函數(shù)結(jié)束時(shí)會(huì)自動(dòng)銷毀。那么,這里的堆棧與上述IL代碼中的堆棧是不是一回事?
請(qǐng)看上述IL程序中有一個(gè)MaxStack指令,查看資料,得知其含義是為evaluation stack保留兩個(gè)槽(slot),注意,這里的堆棧英文原文是evaluation stack,MSDN中文版譯為“計(jì)算堆?!?,slot可用于存放值對(duì)象,大小是可變的。換句話說(shuō),evaluation stack中的每一個(gè)slot可以存放一個(gè)值對(duì)象(對(duì)象引用也可看成是一種“特殊”的值變量,其值代表內(nèi)存地址)或各種CLR直接支持的基本類型數(shù)據(jù)。
從上述IL程序中可以很明顯地看到,局部變量i,j和result絕不會(huì)生存于evaluation stack,因?yàn)樗挥?個(gè)slot,而我們有3個(gè)變量。那它們“活在”在哪兒?
IL程序中引人注目的一句是locals init指令,這提醒我們函數(shù)擁有另一塊內(nèi)存區(qū)域?qū)S糜诖娣啪植孔兞?,所以,聲明為局部變量的值類型并不“活”在evaluation stack中。那么,為何所有的 C#書(包括大名鼎鼎的Jeffrey Richter所著之《.NET框架程序設(shè)計(jì)》)都說(shuō)值類型變量“活”在堆棧中?此堆棧在哪?至少有一點(diǎn)可以肯定,這個(gè)堆棧不會(huì)指的是 evaluation stack。
用ildasm.exe查看程序集清單(manifest),發(fā)現(xiàn)其中有一句:
.stackreserve 0x00100000
上述語(yǔ)句讓CLR在裝入程序集時(shí)保存1M的堆棧空間,這個(gè)空間供托管進(jìn)程的托管線程使用,稱為線程堆棧(Thread Stack)。既是線程堆棧,自然與線程相關(guān),由于.NET托管進(jìn)程可以創(chuàng)建多個(gè)托管線程,因此,每個(gè)線程也應(yīng)該有自己的堆棧(Jeffrey Richter說(shuō)也是1M,查看也是這位老先生寫的《Windows核心編程》,說(shuō)在Win2000在創(chuàng)建線程時(shí)其堆棧大小是可調(diào)整的)。
.NET下每個(gè)托管線程都對(duì)應(yīng)著一個(gè)線程函數(shù),因此函數(shù)中定義的局部變量是在它擁有的線程堆棧中分配,而IL程序中的maxstack指令則從這一個(gè)1M的線程堆棧中再劃出一塊空間來(lái)作為evaluation stack。
考慮一下函數(shù)調(diào)用的問(wèn)題。
IL使用call和callvirt兩條指令調(diào)用特定類型所提供的方法。這就有一個(gè)函數(shù)參數(shù)傳送的問(wèn)題。以call指令為例,MSDN說(shuō)在調(diào)用call指令之前,要將所有的實(shí)參壓入evaluation stack,然后call指令再將其彈出,之后控制才會(huì)轉(zhuǎn)到被調(diào)用的函數(shù),而當(dāng)被調(diào)用的函數(shù)執(zhí)行完畢時(shí),ret指令負(fù)責(zé)“將函數(shù)的返回值”從“被調(diào)用者的堆?!?callee’s evaluation stack)復(fù)制到“調(diào)用者堆?!保╟aller evaluation stack)中。您看MSDN文檔中居然又出現(xiàn)了兩個(gè)堆棧,是否有點(diǎn)暈了嗎?
查看Serge Lidin的書,他給出了這樣一個(gè)圖:
如上圖所示:CLR會(huì)給每一個(gè)被調(diào)用的方法分配三塊內(nèi)存,除了上面講到的兩塊(Evaluation stack和局部變量表Local Variable table),還有一塊是參數(shù)表(Argument table)。
問(wèn)題終于明晰了,call指令完成的工作應(yīng)該是這樣的:
調(diào)用者按要調(diào)用函數(shù)的參數(shù)準(zhǔn)備好實(shí)參,將它們壓入“自己的”evaluation stack中,然后,call指令執(zhí)行,它從調(diào)用者的evaluation stack彈出這些參數(shù),放入被調(diào)用函數(shù)的Argument Table中。一切準(zhǔn)備工作就緒,這時(shí)才開始執(zhí)行被調(diào)用函數(shù)的第一條IL指令。
當(dāng)被調(diào)用函數(shù)執(zhí)行完畢,如果有返回值,這個(gè)值應(yīng)該被放在被調(diào)用函數(shù)自己的evaluation stack中(因?yàn)镮L指令總是與堆棧打交道),然后,ret指令(每個(gè)函數(shù)最后一定是這條指令)將其彈出,再壓入調(diào)用者的evaluation stack中,完成這一工作之后,執(zhí)行流程轉(zhuǎn)回到調(diào)用者。
因此,線程每調(diào)用一個(gè)函數(shù),將導(dǎo)致圖中所示的三塊區(qū)域在1M的線程堆棧中分配給調(diào)用函數(shù),對(duì)于遞歸調(diào)用的情況,后調(diào)用的函數(shù)占用的內(nèi)存區(qū)域?qū)ⅰ皦骸痹谄湔{(diào)用者內(nèi)存區(qū)域之上,每執(zhí)行完一個(gè)函數(shù),對(duì)應(yīng)的棧頂指針移動(dòng)一個(gè)位移(大小剛好等于此函數(shù)先前所占用的內(nèi)存),從而導(dǎo)致這些內(nèi)存被釋放,其中的局部變量不再有效。
分析.NET程序的IL指令還會(huì)得到一些有趣的結(jié)果,后面我會(huì)有更多的文章與網(wǎng)友們進(jìn)行技術(shù)交流。
注:由于手頭的資料不足, 此文所述內(nèi)容僅是本人對(duì)CLR內(nèi)部運(yùn)行機(jī)理的一個(gè)推測(cè),如有錯(cuò)誤,敬請(qǐng)指正。by the way,望有網(wǎng)友能提供更多的MSIL技術(shù)資料信息,在此謝謝了。:-)
轉(zhuǎn)載請(qǐng)注明作者及出處。


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1451065


.NET值類型變量“活”在哪?


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。?!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 永康市| 巫溪县| 泰州市| 长春市| 玉门市| 米林县| 昌图县| 开江县| 醴陵市| 延寿县| 宁城县| 民勤县| 安阳县| 马鞍山市| 棋牌| 崇州市| 微博| 临泉县| 聂荣县| 广丰县| 额敏县| 松江区| 白朗县| 土默特左旗| 延庆县| 桦南县| 普兰县| 山丹县| 永善县| 都兰县| 贵南县| 呼和浩特市| 西宁市| 西吉县| 炉霍县| 扶绥县| 扬州市| 南汇区| 武宣县| 随州市| 盐城市|