新聞中心
前言:

成都創(chuàng)新互聯(lián)公司公司2013年成立,是專(zhuān)業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元萬(wàn)柏林做網(wǎng)站,已為上家服務(wù),為萬(wàn)柏林各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話(huà):18980820575
項(xiàng)目緊趕慢趕總算在年前有了一些成績(jī),所以沉寂了幾周之后,小匹夫也終于有時(shí)間寫(xiě)點(diǎn)東西了。以前匹夫?qū)戇^(guò)一篇文章,對(duì)CIL做了一個(gè)簡(jiǎn)單地介紹,不過(guò)不知道各位看官看的是否過(guò)癮,至少小匹夫覺(jué)得很不過(guò)癮。所以決定寫(xiě)幾篇關(guān)于CIL的文章,即和各位看官一起進(jìn)行個(gè)交流,同時(shí)也是匹夫自己總結(jié)和鞏固一下這些知識(shí)點(diǎn)。俗話(huà)說(shuō)的好,“萬(wàn)事開(kāi)頭,Hello World”,那么作為匹夫總結(jié)CIL的第一篇文章,就從Hello World開(kāi)始吧。當(dāng)然,正式開(kāi)始寫(xiě)CIL代碼之前,我們還有點(diǎn)閑話(huà)要說(shuō),那就是運(yùn)行時(shí)的選擇為何是它?為何是CIL?而CIL為何又是基于堆棧的??jī)?nèi)存或者寄存器難道不是更理想的選擇嗎?
為何是CIL?
開(kāi)始正文內(nèi)容之前,匹夫帶領(lǐng)大家先回顧一下《Mono為何能跨平臺(tái)?聊聊CIL(MSIL)》的簡(jiǎn)要內(nèi)容:首先,用C#寫(xiě)的代碼被C#的編譯器編譯成CIL(當(dāng)然除了C#還有很多其他的語(yǔ)言,比如VB等等),之后再有JIT編譯器在程序運(yùn)行時(shí)即時(shí)編譯或者AOT(或者NGEN)進(jìn)行提前編譯將CIL代碼編譯成對(duì)應(yīng)平臺(tái)的機(jī)器碼,最后運(yùn)行在平臺(tái)上的便是機(jī)器碼。小匹夫在那篇文章中提過(guò),首先將各種不同的語(yǔ)言都統(tǒng)一編譯成CIL,再由CIL編譯成各個(gè)平臺(tái)的機(jī)器碼是跨平臺(tái)的基礎(chǔ)。那么仔細(xì)想想,一定有人會(huì)提出這樣的疑問(wèn),直接從C#編譯到機(jī)器碼,省略掉“多余”的中間語(yǔ)言,是不是也可行呢?這個(gè)問(wèn)題的確值得討論,同時(shí)也為了小匹夫接下來(lái)的文章師出有名,所以首先聊聊CIL的“合法性”(用必要性這個(gè)詞也許更好)問(wèn)題就成了匹夫?qū)戇@篇文章的頭等大事。
論據(jù)一:考慮下“性?xún)r(jià)比”
首先提出我們的論據(jù)一,那就是使用CIL這套體系對(duì)實(shí)現(xiàn)跨平臺(tái)的開(kāi)銷(xiāo)要小的多的多。
引入一個(gè)“多余的”中間語(yǔ)言和兩個(gè)編譯器(C#----->CIL------>機(jī)器碼)聽(tīng)上去總是要比只使用一種編譯器(C#-------->機(jī)器碼)的實(shí)現(xiàn)代價(jià)高的多,因?yàn)槲覀兊哪康氖荂#代碼能編譯成機(jī)器能運(yùn)行的機(jī)器碼,顯然一步到位是最直接有效的方式。相反,引入中間語(yǔ)言之后,我們就需要實(shí)現(xiàn)兩種語(yǔ)言的分析和編譯,看上起的確多此一舉。但如果我們考慮到跨平臺(tái)這個(gè)前提,就會(huì)發(fā)現(xiàn)中間語(yǔ)言是多么的重要。
假設(shè)你可以選擇的語(yǔ)言有N種(比如C#, VB, F#, JScript .NET,Boo...),而我們的目標(biāo)平臺(tái)有M種(win,mac,linux,ios,android...)。那么如果我們采用最直接的編譯方式,即從源代碼直接編譯成機(jī)器碼,那么到底需要多少個(gè)編譯器呢?
答案很直接咯:需要N*M種編譯器。因?yàn)槟阈枰獮槊恳环N語(yǔ)言針對(duì)每一個(gè)平臺(tái)寫(xiě)一個(gè)編譯器。
如果我們采用了中間語(yǔ)言呢?
我們只需要為N種語(yǔ)言寫(xiě)N種編譯器,將它編譯成CIL代碼。再為M種平臺(tái)寫(xiě)M種編譯器,將上一步生成的CIL代碼編譯成M種平臺(tái)的機(jī)器碼。那么這次我們到底需要多少編譯器呢?
答案也很明顯:需要M+N種編譯器。
所以,采用中間語(yǔ)言要比直接編譯代碼的開(kāi)銷(xiāo)小的多得多。
論據(jù)二:實(shí)現(xiàn)的難度
假設(shè),匹夫?qū)τ布Z(yǔ)言一竅不通(當(dāng)然事實(shí)上是這樣的。。。),但卻具備一種分析源代碼語(yǔ)義的特殊天賦(瞎掰的)。那么要實(shí)現(xiàn)從C#到各個(gè)平臺(tái)機(jī)器碼一步到位的編譯,匹夫就要去啃各種目標(biāo)芯片的說(shuō)明,將C#代碼轉(zhuǎn)化成對(duì)應(yīng)芯片的機(jī)器碼。這聽(tīng)上去就像是一條不歸路,因?yàn)槟悴⒉簧瞄L(zhǎng)這個(gè)領(lǐng)域而且工作量巨大,同時(shí)由于不擅長(zhǎng)帶來(lái)的隱患難以估量。
換言之,這個(gè)難度太大了。
但是如果我們通過(guò)對(duì)C#進(jìn)行語(yǔ)義分析,能十分容易的就生成一份和芯片無(wú)關(guān)的CIL代碼,那么實(shí)現(xiàn)的難度相比直接從C#到機(jī)器碼那可是大大的降低了。因?yàn)镃IL語(yǔ)言本身就十分簡(jiǎn)單(至少匹夫這種粗人都能看懂),所以從源代碼到CIL的編譯器實(shí)現(xiàn)就十分容易。同時(shí),也是因?yàn)镃IL語(yǔ)言本身十分簡(jiǎn)單,所以從CIL到機(jī)器碼的編譯器也十分簡(jiǎn)單。
而且即便有新的平臺(tái)出現(xiàn),你也不需要為每種語(yǔ)言都寫(xiě)一個(gè)針對(duì)新平臺(tái)的編譯器,而只需要實(shí)現(xiàn)一個(gè)從CIL到新平臺(tái)機(jī)器碼的編譯器就可以了。
所以可以看到,CIL中間語(yǔ)言的出現(xiàn),大大降低了跨平臺(tái)的實(shí)現(xiàn)難度。
《Mono為何能跨平臺(tái)?聊聊CIL(MSIL)》這篇文章中,小匹夫也給各位列舉了一些CIL的代碼,同時(shí)做了一些解釋?zhuān)闹性诮榻BCIL不依托cpu的寄存器時(shí)寫(xiě)了這樣一句話(huà):
不錯(cuò),CIL是基于堆棧的,也就是說(shuō)CIL的VM(mono運(yùn)行時(shí))是一個(gè)棧式機(jī)。
那么不知道各位看官是否也有這樣的疑問(wèn)呢?那就是~~~~~~~
為什么是棧式機(jī)?直接放在內(nèi)存中不好嗎?
終于要聊聊小匹夫也覺(jué)得挺有趣的一個(gè)話(huà)題了。對(duì)啊,為什么CIL基于堆棧呢?那么我們首先就來(lái)聊聊什么是“棧式機(jī)”。
假如讓你來(lái)...
假如讓你來(lái)設(shè)計(jì)一種機(jī)器語(yǔ)言,同時(shí)實(shí)現(xiàn)一個(gè)簡(jiǎn)單地加法功能,簡(jiǎn)單到什么程度呢?比如a+b等于c這樣好了。那么思路是什么呢?
方案一:使用內(nèi)存
add [a的地址], [b的地址], [結(jié)果的地址也就是c的地址]
當(dāng)機(jī)器遇到add操作符時(shí),它就會(huì)去尋找a的地址和b的地址這兩個(gè)地址中存放的值,然后用balabala的方式將它們求和,并將結(jié)果存放在c的地址。
方案二:使用寄存器
當(dāng)然匹夫也是一個(gè)學(xué)過(guò)匯編的漢子,也了解一點(diǎn)點(diǎn)單片機(jī)的知識(shí),知道有一個(gè)叫做累加器的東西。累加器就屬于寄存器了,它主要用來(lái)儲(chǔ)存計(jì)算所產(chǎn)生的中間結(jié)果,最后將其轉(zhuǎn)存到其它寄存器或內(nèi)存中。所以使用累加器的思路也很簡(jiǎn)單,一開(kāi)始將累加器設(shè)定為0,每個(gè)數(shù)字依序地被加到累加器中,當(dāng)所有的數(shù)字都被加入后,結(jié)果才寫(xiě)回到主內(nèi)存中。
方案三:使用堆棧
等等,這個(gè)部分介紹的不是棧式機(jī)嗎?怎么感覺(jué)有點(diǎn)跑題呢?好吧,拉回思緒,讓我們?cè)賮?lái)考慮下使用堆棧如何實(shí)現(xiàn)這個(gè)簡(jiǎn)單地加法功能呢?
push a push b add pop c
add操作符首先將a,b彈出堆棧,然后將二者相加,再將結(jié)果壓棧。那么,使用了這種方案的虛擬機(jī),就被稱(chēng)為“棧式機(jī)”。
所以如果要回答為何CIL的選擇是使用堆棧,那么就繞不過(guò)堆棧和另外兩種方案的比較。
首先看一下我們做這種簡(jiǎn)單加法時(shí),硬件需要為我們提供一些什么呢?對(duì),就是存放這些值的臨時(shí)空間。所謂的臨時(shí)空間,就是說(shuō)存儲(chǔ)這個(gè)值的空間只有在需要這個(gè)值的時(shí)候才有用,其余的時(shí)候你并不需要關(guān)心這個(gè)空間或者說(shuō)它的地址到底是什么。假設(shè)我們已經(jīng)定義了一些操作符,比如Allocate用來(lái)分配內(nèi)存,Call用來(lái)調(diào)用函數(shù),Add用來(lái)求和,Store則是用來(lái)存儲(chǔ)數(shù)據(jù)。
首先我們直接使用內(nèi)存來(lái)運(yùn)行CIL,那么遇到這樣的表達(dá)式:
x = A() + B() + C() + 100
機(jī)器首先要為A()在內(nèi)存上分配空間用來(lái)保存它的返回值,然后調(diào)用A()并將A()的返回值保存在之前分配給它的地址中,我們就管它叫做ret1好了。之后為B()在內(nèi)存上分配空間來(lái)保存B()的返回值,接著調(diào)用B(),同樣將B()的返回值保存在剛才分配給它的內(nèi)存中,我們暫時(shí)稱(chēng)呼它ret2。這時(shí),我們遇到了第一個(gè)“+”號(hào),所以此時(shí)會(huì)為ret1和ret2相加的結(jié)果在內(nèi)存上分配一個(gè)空間,并且將ret1和ret2相加,并將結(jié)果保存在剛剛分配的內(nèi)存中(我們稱(chēng)為sum1),之后的過(guò)程以此類(lèi)推。
- Allocate ret1 //為A()的返回值分配臨時(shí)空間ret1
- Call A(),ret1 //調(diào)用A()并將結(jié)果保存在ret1
- Allocate ret2 //為B()的返回值分配臨時(shí)空間ret2
- Call B(),ret2 //調(diào)用B()并將結(jié)果保存在ret2
- Allocate sum1 //為第一次相加的結(jié)果分配臨時(shí)空間sum1
- Add ret1,ret2,sum1 //使用Add操作符將ret1和ret2中的內(nèi)容相加,并將結(jié)果保存在sum1中。
- ...
可以看到這樣的CIL代碼在每一步真正的邏輯執(zhí)行之前,都會(huì)先在內(nèi)存上分配一塊臨時(shí)空間,用來(lái)存儲(chǔ)我們此時(shí)需要的數(shù)據(jù)。如果使用堆棧,這個(gè)步驟是不需要,因?yàn)槟銓⒛阈枰臄?shù)據(jù)存儲(chǔ)在了堆棧之中,而非在內(nèi)存上臨時(shí)去分配空間。所以,使用堆棧時(shí),CIL代碼看上去也許像是這樣的:
- push x的地址 // 將x的地址壓棧
- call A() // 現(xiàn)在堆棧中包含x的地址和A()的返回值ret1
- call B() // 現(xiàn)在堆棧中包換x的地址,ret1,B()的返回值ret2
- add // 現(xiàn)在堆棧中包含x的地址,ret1 + ret2的結(jié)果sum1
- call C() // 現(xiàn)在堆棧中包含x的地址,sum1和C()的返回值ret3
- add // 現(xiàn)在堆棧中包含x的地址, ret1+ret2+ret3的返回值sum2
- push 100 // 現(xiàn)在堆棧中包含x的地址,sum2,以及100
- add // 現(xiàn)在堆棧中包含x的地址, ret1+ret2+ret3+100的和sum3
- store //將sum3存在x的地址中。
同時(shí),我們還可以看到如果CIL直接使用內(nèi)存的話(huà),由于在內(nèi)存上的空間是臨時(shí)分配的,所以CIL代碼在運(yùn)行時(shí)需要帶上它的操作數(shù)地址以及返回地址,比如上例中的Add ret1,ret2,sum1,因?yàn)槿绻桓嬖V它這些地址,它就不知道該從何處得到數(shù)據(jù),并將返回的數(shù)據(jù)放在何處。
所以直接使用內(nèi)存來(lái)運(yùn)行CIL代碼,會(huì)使得CIL代碼變得十分的臃腫不堪,而且要做很多多余的工作。所以不直接使用內(nèi)存,而是使用堆棧的原因就是因?yàn)椋喝绻覀儍H僅只是為了臨時(shí)存儲(chǔ)一些值,而在使用完這些值之后我們就不再關(guān)心這塊空間如何如何,顯然使用堆棧要比直接使用內(nèi)存方便的多,簡(jiǎn)潔的多。
至于為何不使用寄存器,小匹夫在上文提到的文章中已經(jīng)解釋過(guò)了。簡(jiǎn)單的講就是因?yàn)楹?jiǎn)單。
好啦,到此為CIL正名的過(guò)程就結(jié)束啦。那么下面就開(kāi)始首尾呼應(yīng),結(jié)尾點(diǎn)題,從Hello World開(kāi)始踏上我們的CIL語(yǔ)言的征程吧~~
#p#
Hello Wolrd 你好,沃爾德
本文開(kāi)篇就提到了那句名言:“萬(wàn)事開(kāi)頭,Hello World”。那么我們第一個(gè)CIL語(yǔ)言的程序,就從Hello World開(kāi)始吧。因?yàn)槠シ蚴褂玫氖莔ac機(jī)器,所以編譯.il文件所使用的工具是mono的ilasm。
那么匹夫就先新建一個(gè).il文件,起名就叫做chen.il好了。
與C#不同,CIL并不要求方法必須要屬于一個(gè)類(lèi)。所以,我們無(wú)需定義一個(gè)類(lèi),只需要聲明一個(gè)主函數(shù)(按照C#的說(shuō)法main)即可。其實(shí)在CIL中我們應(yīng)該管這種函數(shù)叫做“entrypoint”,也就是入口函數(shù)。只要定義了“entrypoint”,函數(shù)叫不叫main都無(wú)關(guān)緊要,為了演示這一點(diǎn),我們的函數(shù)名就叫做Fanyou好了。
那么小匹夫就這樣寫(xiě)一下咯:
上面就是小匹夫的Fanyou方法的定義了。和一般的語(yǔ)言一樣,包括方法簽名和方法體。但是在CIL語(yǔ)言中,方法的定義有以下需要注意的地方:
- 方法的定義以.method作為標(biāo)識(shí),可以在類(lèi)中聲明,也可以在類(lèi)外聲明。
- 和C#一樣,CIL程序的入口也必須是靜態(tài)的,也就是意味著調(diào)用這個(gè)入口函數(shù)并不需要某個(gè)類(lèi)的實(shí)例。當(dāng)然,使用static關(guān)鍵字來(lái)標(biāo)識(shí)。
- 入口的標(biāo)識(shí).entrypoint,這個(gè)標(biāo)志表明了該方法是CIL程序的入口。所以咯,只有一個(gè)函數(shù)能擁有.entrypoint標(biāo)識(shí)。
- .maxstack這個(gè)標(biāo)識(shí)表明了預(yù)計(jì)使用的堆棧槽,這里是1,因?yàn)槲覀冎皇前选癏ello World”這個(gè)字符串壓棧。舉個(gè)例子,如果像上文那樣做2個(gè)數(shù)相加的加法,則需要2個(gè)堆棧槽,首先需要將2個(gè)數(shù)壓棧,之后add操作符將2個(gè)數(shù)彈出并求和,最后將結(jié)果壓棧。所以最多需要2個(gè)棧槽。
- ldstr操作符將“Hello World”壓棧,供之后的WriteLine方法使用。
- call調(diào)用了mscorlib程序集中System.Console類(lèi)的WriteLine方法。這里call指明了WriteLine完整的簽名(void [mscorlib]System.Console::WriteLine(string))所以運(yùn)行時(shí)可以選擇WriteLine的正確地重載。
- ret操作符則將結(jié)果返回給調(diào)用者。在這里,作為入口函數(shù)的返回,也意味著應(yīng)用運(yùn)行的結(jié)束。
- 有一些同學(xué)可能也看過(guò)很多CIL語(yǔ)言的代碼,是不是發(fā)現(xiàn)它們每一條語(yǔ)句之前往往有一個(gè)“IL_0000:”這樣的東東?但是匹夫你寫(xiě)的代碼里沒(méi)有??!是不是你寫(xiě)錯(cuò)了?NO,NO,那個(gè)IL_XXXX其實(shí)僅僅是行號(hào),是不會(huì)影響程序的運(yùn)行的。
好啦,一個(gè)簡(jiǎn)單地Hello World的確能帶來(lái)一些最基本的知識(shí)點(diǎn),但是這個(gè).il文件編譯之后能運(yùn)行嗎?答案是NO。因?yàn)樯厦娴牡?點(diǎn)也說(shuō)了,調(diào)用了mscorlib程序集。但是我們貌似沒(méi)有引入什么程序集啊?所以我們還要加入一些程序集的信息才可以哦。那么完整的代碼如下了:
然后,讓我們編譯并且運(yùn)行一下,看看我們寫(xiě)的實(shí)現(xiàn)了Fanyou方法,輸出Hello World的CIL代碼到底是否可以運(yùn)行吧!
運(yùn)行結(jié)果:
首先
ilasm chen.il
對(duì)chen.il這個(gè)CIL文件進(jìn)行編譯,生成的結(jié)果是chen.exe
之后再運(yùn)行chen.exe
mono chen.exe
可以看到屏幕上輸出了“Hello World”。
OK,大功告成!
原文出自:http://www.cnblogs.com/murongxiaopifu/p/4257264.html
網(wǎng)站標(biāo)題:你還不知道CIL?一個(gè)讓你開(kāi)發(fā)項(xiàng)目更有性?xún)r(jià)比的中間語(yǔ)言
網(wǎng)頁(yè)網(wǎng)址:http://fisionsoft.com.cn/article/cojoiii.html


咨詢(xún)
建站咨詢(xún)
