第一個 C 語言編譯器是怎樣編寫的?

首先向C語言之父Dennis Ritchie致敬! 程序員

 

當今幾乎全部的實用的編譯器/解釋器(如下統稱編譯器)都是用C語言編寫的,有一些語言好比Clojure,Jython等是基於JVM或者說是用Java實現的,IronPython等是基於.NET實現的,可是Java和C#等自己也要依靠C/C++來實現,等因而間接調用了C。因此衡量某種高級語言的可移植性其實就是在討論ANSI/ISO C的移植性。編程

C語言是很低級的語言,不少方面都近似於彙編語言,在《Intel 32位彙編語言程序設計》一書中,甚至介紹了手工把簡單的C語言翻譯成彙編的方法。對於編譯器這種系統軟件,用C語言來編寫是很天然不過的,即便是像Python這樣的高級語言依然在底層依賴於C語言(舉Python的例子是由於Intel的黑客正在嘗試讓Python不須要操做系統就能運行——其實是免去了BIOS上的一次性C代碼)。如今的學生,學過編譯原理後,只要有點編程能力的均可以實現一個功能簡單的類C語言編譯器。數組

但是問題來了,不知道你有沒有想過,你們都用C語言或基於C語言的語言來寫編譯器,那麼世界上第一個C語言編譯器又是怎麼編寫的呢?這不是一個「雞和蛋」的問題……數據結構

仍是讓咱們回顧一下C語言歷史:1970年Tomphson和Ritchie在BCPL(一種解釋型語言)的基礎上開發了B語言,1973年又在B語言的基礎上成功開發出瞭如今的C語言。在C語言被用做系統編程語言以前,Tomphson也用過B語言編寫過操做系統。可見在C語言實現之前,B語言已經能夠投入實用了。所以第一個C語言編譯器的原型徹底多是用B語言或者混合B語言與PDP彙編語言編寫的。咱們如今都知道,B語言的執行效率比較低,可是若是所有用匯編語言來編寫,不只開發週期長、維護難度大,更可怕的是失去了高級程序設計語言必需的移植性。因此早期的C語言編譯器就採起了一個取巧的辦法:先用匯編語言編寫一個C語言的一個子集的編譯器,再經過這個子集去遞推完成完整的C語言編譯器。詳細的過程以下:編程語言

先創造一個只有C語言最基本功能的子集,記做C0語言,C0語言已經足夠簡單了,能夠直接用匯編語言編寫出C0的編譯器。依靠C0已有的功能,設計比C0複雜,但仍然不完整的C語言的又一個子集C1語言,其中C0屬於C1,C1屬於C,用C0開發出C1語言的編譯器。在C1的基礎上設計C語言的又一個子集C2語言,C2語言比C1複雜,可是仍然不是完整的C語言,開發出C2語言的編譯器……如此直到CN,CN已經足夠強大了,這時候就足夠開發出完整的C語言編譯器的實現了。至於這裏的N是多少,這取決於你的目標語言(這裏是C語言)的複雜程度和程序員的編程能力——簡單地說,若是到了某個子集階段,能夠很方便地利用現有功能實現C語言時,那麼你就找到N了。下面的圖說明了這個抽象過程:函數

那麼這種大膽的子集簡化的方法,是怎麼實現的,又有什麼理論依據呢?先介紹一個概念,「自編譯」Self-Compile,也就是對於某些具備明顯自舉性質的強類型(所謂強類型就是程序中的每一個變量必須聲明類型後才能使用,好比C語言,相反有些腳本語言則根本沒有類型這一說法)編程語言,能夠藉助它們的一個有限小子集,經過有限次數的遞推來實現對它們自身的表述,這樣的語言有C、Pascal、Ada等等,至於爲何能夠自編譯,能夠參見清華大學出版社的《編譯原理》,書中實現了一個Pascal的子集的編譯器。總之,已經有計算機科學家證實了,C語言理論上是能夠經過上面說的CVM的方法實現完整的編譯器的,那麼其實是怎樣作到簡化的呢?這張圖是否是有點熟悉?對了就是在講虛擬機的時候見到過,不過這裏是CVM(C Language Virtual Machine),每種語言都是在每一個虛擬層上能夠獨立實現編譯的,而且除了C語言外,每一層的輸出都將做爲下一層的輸入(最後一層的輸出就是應用程序了),這和滾雪球是一個道理。用手(彙編語言)把一小把雪結合在一塊兒,一點點地滾下去就造成了一個大雪球,這大概就是所謂的0生1,1生C,C生萬物吧?優化

下面是C99的關鍵字: 操作系統

 

仔細看看,其實其中有不少關鍵字是爲了幫助編譯器進行優化的,還有一些是用來限定變量、函數的做用域、連接性或者生存週期(函數沒有)的,這些在編譯器實現的早期根本沒必要加上,因而能夠去掉auto, restrict, extern, volatile, const, sizeof, static, inline, register, typedef,這樣就造成了C的子集,C3語言,C3語言的關鍵字以下: 翻譯

 

再想想,發現C3中其實有不少類型和類型修飾符是沒有必要一次性都加上去的,好比三種整型,只要實現int就好了,所以進一步去掉這些關鍵詞,它們是:unsigned, float, short, char(char 是 int), signed, _Bool, _Complex, _Imaginary, long,這樣就造成了咱們的C2語言,C2語言關鍵字以下:設計

 

繼續思考,即便是隻有18個關鍵字的C2語言,依然有不少高級的地方,好比基於基本數據類型的複合數據結構,另外咱們的關鍵字表中是沒有寫運算符的,在C語言中的複合賦值運算符->、運算符的++、– 等過於靈活的表達方式此時也能夠徹底刪除掉,所以能夠去掉的關鍵字有:enum, struct, union,這樣咱們能夠獲得C1語言的關鍵字: 

接近完美了,不過最後一步手筆天然要大一點。這個時候數組和指針也要去掉了,另外C1語言其實仍然有很大的冗雜度,好比控制循環和分支的都有多種表述方法,其實均可簡化成一種,具體的來講,循環語句有while循環,do…while循環和for循環,只須要保留while循環就夠了;分支語句又有if…{}, if…{}…else, if…{}…else if…, switch,這四種形式,它們均可以經過兩個以上的if…{}來實現,所以只須要保留if,…{}就夠了。但是再一想,所謂的分支和循環不過是條件跳轉語句罷了,函數調用語句也不過是一個壓棧和跳轉語句罷了,所以只須要goto(未限制的goto)。所以大膽去掉全部結構化關鍵字,連函數也沒有,獲得的C0語言關鍵字以下:

 

只有5個關鍵字,已經徹底能夠用匯編語言快速的實現了。經過逆向分析咱們還原了第一個C語言編譯器的編寫過程,也感覺到了前輩科學家們的智慧和勤勞!咱們都不過是巨人肩膀上的灰塵罷了!0生1,1生C,C生萬物,實在巧妙!

相關文章
相關標籤/搜索