編譯器之自舉

**要閱讀本文,不需要太高深的編譯原理知識爆办,甚至不需要編譯相關(guān)的知識难咕。但是本文也不是面向?qū)﹄娔X一無所知的讀者的,你至少要知道:

  1. 不管是exe可執(zhí)行文件還是Linux下的程序,都是一些二進(jìn)制代碼余佃,我們稱之為機(jī)器語言暮刃。這些代碼的執(zhí)行和系統(tǒng)以及CPU都有關(guān)。

  2. 大部分情況下爆土,編譯器是一種將高級語言翻譯成機(jī)器語言的程序椭懊。而任何程序本身也是一些機(jī)器語言的代碼。

  3. 無論是高級語言步势、匯編語言還是機(jī)器語言氧猬,實(shí)際上都是等價的,唯一有區(qū)別的是坏瘩,越高級的語言寫起來越容易(所以我們傾向于盡量多書寫高級語言代碼)狂窑;同時,機(jī)器語言是可以直接運(yùn)行的桑腮。如果你覺得上面三條可以理解或者可以理解一部分泉哈,那么請繼續(xù)閱讀。另外破讨,本文中提到的無論是編譯的過程還是語言的分類甚至是一些例子丛晦,都可以看做是一個簡化的模型。實(shí)際上提陶,很少有一個exe程序就可以運(yùn)行的編譯器烫沙,現(xiàn)代的編譯器都是十分復(fù)雜的。不過隙笆,考慮這些簡化模型對我們沒有任何壞處锌蓄,它們和實(shí)際情況相差并不很大。**

首先我來詳細(xì)解釋一下高級語言和機(jī)器語言撑柔。
語言的所謂“高級”瘸爽,實(shí)際上界定不是那么明確。不過我們可以確定的是:C++铅忿、Java剪决、Python之流比匯編語言更高級,而匯編語言比機(jī)器語言更高級檀训。這里提到一個匯編語言和機(jī)器語言的區(qū)分柑潦。可能有些讀者不明白這兩個概念的區(qū)別峻凫,實(shí)際上很簡單渗鬼,匯編語言是我們會看到的那些MOV、JMP等命令組成的語言荧琼,而機(jī)器語言則純粹是是各種01串譬胎。我想沒有人會愿意寫一個機(jī)器語言的程序——實(shí)際上肛循,我們學(xué)校的計(jì)算機(jī)組成實(shí)驗(yàn)都是用Verilog HDL這種高級語言來完成的,而嵌入式原理實(shí)驗(yàn)也是用一種匯編語言完成的银择;即使是做處理器和單片機(jī),也不會有人愿意去寫機(jī)器語言累舷,畢竟一大堆01串太坑爹了浩考。試想,讓你用C++寫一個從1輸出到100的程序被盈,幾行代碼就可以搞定析孽;而匯編語言則可能需要幾十行;機(jī)器語言呢只怎,Oh my god袜瞬,一大堆01000101110110101011111001……,看都看不懂身堡,還寫啥啊邓尤。
但是上面也提到了,機(jī)器語言和高級語言的區(qū)別是贴谎,它可以直接運(yùn)行汞扎。比如exe程序,實(shí)際上它內(nèi)部存儲的就是一些機(jī)器語言的代碼擅这,機(jī)器可以直接閱讀這些代碼并在處理器中運(yùn)行它們(這里說的不是完全準(zhǔn)確澈魄,比如.NET編譯出的exe程序?qū)嶋H上是一段中間代碼,由CLR解釋成機(jī)器代碼才能運(yùn)行——不過這可以暫且忽略仲翎,就當(dāng)作我說的是一個簡化的模型)痹扇。當(dāng)然程序的運(yùn)行是依賴機(jī)器架構(gòu)和系統(tǒng)的,不然Wine什么的也就沒有用了(什么是Wine溯香?WINE = WINE Is Not an Emulator鲫构!有趣的名稱遞歸定義還有很多,不過與本文無關(guān)玫坛,請自行Google)芬迄。而實(shí)際上,是機(jī)器架構(gòu)不同還是系統(tǒng)不同昂秃,并不是我們考慮的問題禀梳。我們考慮的問題只是,一段代碼在A機(jī)器和X系統(tǒng)下能否運(yùn)行肠骆,換到B機(jī)器和Y系統(tǒng)下又能否運(yùn)行算途。就算在A機(jī)器和X系統(tǒng)下能運(yùn)行,如果換成了A機(jī)器和Y系統(tǒng)之后不能運(yùn)行了蚀腿,那對于我們這也可以看做兩臺不同的機(jī)器(也就是說嘴瓤,A機(jī)器和Y系統(tǒng)實(shí)際上就可以看做一個新的機(jī)器B)扫外。所以之后的描述中,我們不考慮操作系統(tǒng)的情況廓脆,而是只考慮機(jī)器筛谚,我們編號為a、b等停忿,而它們上面可以運(yùn)行的機(jī)器語言我們編號為A驾讲、B等。
以上幾段的關(guān)鍵點(diǎn)是:無論是機(jī)器語言還是高級語言席赂,實(shí)際上都是代碼吮铭。在之后的討論中,我們不應(yīng)該將它們區(qū)別對待颅停。雖然機(jī)器語言代碼和高級語言略有不同谓晌,它可以直接在機(jī)器上運(yùn)行,但是對于編譯過程癞揉,它們沒有任何區(qū)別纸肉。

上面的問題了解了之后,我再來簡單說一下編譯器喊熟。那編譯器是什么毁靶?這要從兩個方面來解釋。
首先逊移,編譯器本身的功能是预吆,將一種語言S的代碼轉(zhuǎn)化為一種語言T的代碼。這里稍微了解編譯器的讀者可能有疑問了:我用的gcc之類的編譯器胳泉,明明是把C語言代碼編譯成了一個exe程序拐叉,并不是把S語言的代碼編譯成了T語言的代碼啊扇商?如果你也有這個疑問凤瘦,請重新閱讀以上幾段——我在前面已經(jīng)提過了,“任何程序本身也是一些機(jī)器語言的代碼”案铺。也就是說蔬芥,我們這里把機(jī)器語言也看作一種語言,只不過是很低級的語言控汉;而編譯器就可以將C語言這種高級語言S轉(zhuǎn)化為機(jī)器語言這種低級語言T笔诵,恰好這種低級語言還是可以在機(jī)器t上直接運(yùn)行的。其次姑子,大部分情況下乎婿,編譯器自己也是一段程序,那么它也可以看做是一段代碼街佑。而編譯器也會有它的源代碼谢翎,這個源代碼就是一種高級語言的代碼捍靠,我們這時仍然叫它編譯器。隨便舉個例子森逮,對于gcc.exe榨婆,這個程序是一種機(jī)器語言A的代碼,它可以直接在a機(jī)器上運(yùn)行褒侧。而它的功能則是良风,將C語言的代碼轉(zhuǎn)化為機(jī)器語言A的代碼(一個C語言的編譯器),使得本身無法直接運(yùn)行的C語言程序變得可以在a機(jī)器上運(yùn)行璃搜。而在得到gcc.exe之前,我們一定也有一段代碼鳞上,它是用來生成gcc.exe這個程序的这吻,這段代碼可能是一個高級語言的代碼,比如匯編語言篙议。那這時候唾糯,這段匯編語言的代碼即使不能直接在電腦上運(yùn)行,我們依然說這是一個C語言的編譯器鬼贱。
有了這兩點(diǎn)移怯,我們就可以總結(jié)出一個編譯器的特征:1. 本身是一段代碼,假設(shè)是A語言的代碼这难,A可以是機(jī)器語言舟误,也可以不是;2. 可以接收一段代碼姻乓,假設(shè)是S語言的代碼嵌溢,S一般來說是高級語言,但理論上也可以不是蹋岩;3. 可以將接收到的S語言代碼在內(nèi)部轉(zhuǎn)換后輸出一段代碼赖草,假設(shè)是T語言的代碼,T有可能是機(jī)器語言剪个,但也有可能是一種高級語言秧骑。這樣,一個編譯器就可以被表示為A(S –> T)扣囊,表示編譯器本身的代碼是A語言乎折,可以接受一個S語言代碼作為輸入,同時產(chǎn)生相同功能的T語言代碼作為輸出侵歇。之后我們的編譯器都會這么表示笆檀,請務(wù)必牢記。
以上幾段的關(guān)鍵點(diǎn)是:編譯器是什么盒至?從本質(zhì)上看是一段代碼酗洒,而從功能上看可以將一段代碼等價地轉(zhuǎn)化為另一段代碼士修。所以,我們用A(S –> T)表示一個編譯器樱衷,這個編譯器本身是由A語言寫成的棋嘲,它的功能是將S語言代碼等價地轉(zhuǎn)換為T語言代碼。當(dāng)A語言是機(jī)器語言的時候矩桂,這個編譯器就是可以使用的沸移。


有了之前的知識基礎(chǔ),終于可以開始講自展了侄榴。
不知道讀了之前的內(nèi)容雹锣,你有沒有這樣一個疑問:既然編譯器本身是一段代碼,那么如果想編譯一個編譯器癞蚕,就需要更早的編譯器來進(jìn)行編譯操作蕊爵。而編譯這個更早的編譯器還需要更更早的編譯器——長此以往,第一個編譯器是怎么產(chǎn)生的呢桦山?難道是直接用機(jī)器語言書寫的攒射?如果能這么想,那么你就猜對了恒水。第一個編譯器一定是用機(jī)器語言寫出的会放。實(shí)際上,很久以前的程序員還在紙帶上打孔編程呢钉凌,他們也照樣樂在其中咧最。機(jī)器語言只是很難書寫,并不是不能書寫御雕。但是窗市,畢竟用這么難書寫的語言寫一個C++編譯器,誰都不會愿意去干饮笛。所以咨察,第一個編譯器的功能一定是很簡單的。而人們會用這很簡單的編譯器的語言福青,去寫一個稍微復(fù)雜一些的編譯器摄狱;然后再用這個新的編譯器,去寫一個更復(fù)雜的編譯器无午;最終得到一個很復(fù)雜的編譯器媒役,比如C++編譯器——好了,如果能夠理解這個過程宪迟,那么你實(shí)際上幾乎就理解了自展酣衷。
所謂自展,實(shí)際上就是用一個功能不太完善的編譯器支持的語言寫代碼次泽,然后放到這個編譯器上去編譯穿仪,產(chǎn)生一個比自己更完善的編譯器的過程席爽。用一個不太恰當(dāng)?shù)睦觼砻枋觯褪俏覀円呀?jīng)有了一個C語言的編譯器啊片,然后我們用C語言寫一個C++的編譯器代碼只锻,并用C語言編譯器編譯這個代碼生成可以運(yùn)行的C++編譯器(之所以不太恰當(dāng),是因?yàn)镃語言不是C++語言的嚴(yán)格子集)紫谷。也就是說齐饮,我們有一個編譯器A(C –> A),現(xiàn)在寫一個編譯器C(C++ –> A)笤昨,將后者放入前者中進(jìn)行編譯祖驱,即A(C(C++ –> A) –> A),得到一個可以執(zhí)行的編譯器A(C++ –> A)瞒窒。
以上幾段的關(guān)鍵點(diǎn)是:自展過程捺僻,實(shí)際上就是用低級語言先實(shí)現(xiàn)一個簡單的編譯器,然后用這個編譯器的語言再去編寫一個更高級的編譯器——這個新編譯器是舊編譯器的擴(kuò)展——的過程根竿。

自展很簡單陵像?那下面就是本文的重頭戲——交叉編譯了就珠。
交叉編譯這個概念寇壳,沒有自展這個概念那么準(zhǔn)確。所以先看一個問題:如果你想寫一個手機(jī)系統(tǒng)上的程序妻怎,你會怎么做呢壳炎?我們可能會想到,開發(fā)一個手機(jī)上的編譯器逼侦,然后把程序放到上面編譯成手機(jī)可以運(yùn)行的機(jī)器代碼匿辩。但是這樣就有一個很大的問題了,那就是手機(jī)的運(yùn)行速度和電腦相比十分緩慢(雖然現(xiàn)在手機(jī)的CPU已經(jīng)很強(qiáng)悍了榛丢,但是手機(jī)的內(nèi)存一向很小铲球,不太足以運(yùn)行編譯器)。實(shí)際上晰赞,在現(xiàn)實(shí)中我們的做法是稼病,在電腦上完成編譯過程,然后直接把在手機(jī)上可以運(yùn)行的機(jī)器代碼拷貝到手機(jī)內(nèi)掖鱼。這樣就不受限于編譯時手機(jī)的內(nèi)存和CPU限制了然走。這樣一個過程,就叫做交叉編譯戏挡。當(dāng)然交叉編譯也有其它的應(yīng)用芍瑞,比如有時候我們也需要在一臺電腦a上生成另外一臺電腦b上同一個語言的編譯器,比如在Windows下編譯一個Linux的編譯器褐墅。這個問題比前面的問題還要多一步編譯操作拆檬,我們不妨在后面的討論中將這兩個問題稱作問題1(前者)和問題2(后者)洪己。
為了方便起見,我們把這兩個問題用之前的表示方法來書寫一下秩仆。首先码泛,將電腦看做是機(jī)器a,手機(jī)看作是機(jī)器b澄耍。我們現(xiàn)在手里擁有的是某語言S在a機(jī)器上的編譯器A(S –> A)噪珊。對于問題1,我們需要最終生成一個在a機(jī)器上編譯b機(jī)器代碼的編譯器齐莲,即A(S –> B)痢站。而對于問題2糖驴,我們則是需要一個在b機(jī)器上生成b機(jī)器代碼的編譯器齿穗,B(S –> B)。而我們可以做到的是漩蟆,用高級語言S寫一個自身的編譯器(可以是a機(jī)器的也可以是b機(jī)器的芒填,不過這個問題中只有后者會被用到)呜叫,即S(S –> B)。好殿衰,現(xiàn)在我們就有了兩個代碼朱庆,A(S –> A)和S(S –> B),其中只有第一個代碼是可以在a機(jī)器上運(yùn)行的闷祥。我們要從這兩個代碼中得到兩個新的代碼娱颊,那就是A(S –> B)和B(S –> B)。

首先先考慮如何得到一個A(S –> B)凯砍,即一個在a機(jī)器上運(yùn)行的可以將S語言代碼編譯為b機(jī)器代碼的編譯器箱硕。其實(shí)寫成現(xiàn)在的形式,這一點(diǎn)如何做到已經(jīng)很簡單了悟衩。那就是將S(S –> B)放入A(S –> A)中編譯剧罩,即A(S(S –> B) –> A),這樣就可以得到A(S –> B)座泳。這一步因?yàn)槭褂玫木幾g器是A(S –> A)惠昔,所以只需要在A機(jī)器上執(zhí)行。這時钳榨,可以發(fā)現(xiàn)第一個問題已經(jīng)解決了舰罚。我們這時就可以利用得到的A(S –> B)在a機(jī)器(電腦)上編譯b機(jī)器(手機(jī))的代碼了。

然后在考慮如何得到一個B(S –> B)薛耻∮眨可以看到,我們之前一步的時候有了一個A(S –> B),而最開始還擁有一個S(S –> B)饲漾。這樣蝙搔,我們只需要將S(S –> B)放入A(S –> B)中編譯,即A(S(S –> B) –> B)考传,這樣就可以得到一個B(S –> B)了吃型。這樣,我們用前一步得到的編譯器A(S –> B)僚楞,在a機(jī)器(Windows)上運(yùn)行勤晚,又得到了一個新的編譯器B(S –> B),它可以在b機(jī)器(Linux)下編譯b機(jī)器(Linux)的代碼泉褐。

可以看到赐写,我們從最開始的編譯器A(S –> A)和代碼S(S –> B)得到最終代碼B(S –> B)的過程中,所有的步驟都是在A機(jī)器上運(yùn)行的膜赃,完全沒有用到B機(jī)器挺邀。這就是交叉編譯的有趣之處!使用交叉編譯跳座,可以在很多情況下省去不少的麻煩端铛。
另外解釋一個問題,為什么不直接編寫一個A(S –> B)疲眷?因?yàn)锳是機(jī)器代碼禾蚕,而之前也提到過了,“我們傾向于盡量多書寫高級語言代碼”咪橙,因?yàn)闄C(jī)器語言十分難以理解和書寫夕膀。所以虚倒,我們寫了一個高級語言代碼S(S –> B)美侦,將它放到已有的A(S –> A)上去編譯(為什么是已有的?這個問題實(shí)際上不需要回答魂奥,因?yàn)檫@里假設(shè)a機(jī)器是一臺很常用的機(jī)器(比如安裝了Windows系統(tǒng)的PC機(jī))菠剩,所以A(S –> A)是一個很普通的編譯器,它的存在性無需證明耻煤;A(S –> A)的書寫可以利用之前提到的自展完成)具壮,最終得到了一個需要的B(S –> B)。
以上幾段的關(guān)鍵點(diǎn)是:關(guān)鍵點(diǎn)太多了有木有99准恕!這個怎么總結(jié)呢……如果看不太懂交叉編譯的過程的話炮赦,可以暫且把a(bǔ)機(jī)器當(dāng)作一個安裝了Windows的PC機(jī)怜跑,把b機(jī)器當(dāng)作一臺手機(jī)或者一個裝了Linux的PC機(jī),然后再帶著這個理解重新閱讀整個過程。實(shí)際上性芬,交叉編譯解決的兩個問題分別都只需要一步操作峡眶,所以交叉編譯沒有聽起來那么復(fù)雜!不要自己嚇到自己就好了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末植锉,一起剝皮案震驚了整個濱河市辫樱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俊庇,老刑警劉巖狮暑,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辉饱,居然都是意外死亡心例,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門鞋囊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來止后,“玉大人,你說我怎么就攤上這事溜腐∫胫辏” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵挺益,是天一觀的道長歉糜。 經(jīng)常有香客問我,道長望众,這世上最難降的妖魔是什么匪补? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮烂翰,結(jié)果婚禮上夯缺,老公的妹妹穿的比我還像新娘。我一直安慰自己甘耿,他們只是感情好踊兜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佳恬,像睡著了一般捏境。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毁葱,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天垫言,我揣著相機(jī)與錄音,去河邊找鬼倾剿。 笑死筷频,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播截驮,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笑陈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葵袭?” 一聲冷哼從身側(cè)響起涵妥,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坡锡,沒想到半個月后蓬网,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹉勒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年帆锋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禽额。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锯厢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脯倒,到底是詐尸還是另有隱情实辑,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布藻丢,位于F島的核電站剪撬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悠反。R本人自食惡果不足惜残黑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斋否。 院中可真熱鬧梨水,春花似錦、人聲如沸如叼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笼恰。三九已至,卻和暖如春歇终,著一層夾襖步出監(jiān)牢的瞬間社证,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工评凝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留追葡,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像宜肉,于是被迫代替她去往敵國和親匀钧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容