這是來自我的星球的一個提問:“C語言本身用什麼語言寫的?”
換個角度來問,其實是:C語言在執行之前,得編譯才行,那C語言的編譯器從哪裡來? 用什麼語言來寫的?如果是用C語言本身來寫的,到底是先有蛋還是先有雞?
我們假設世界上不存在任何編譯器, 先從機器語言說起,看看怎麼辦。
機器語言可以直接被CPU執行,不需要編譯器。
然後是組合語言, 組合語言雖然只是機器語言的助記符,但是也需要編譯成機器語言才能執行,沒辦法只能用機器語言來寫這第一個編譯器了(以後就不用了)。
組合語言的問題解決了,就往前邁進了一大步,這時候就可以用匯編語言去寫C語言的編譯器,我們說這是C編譯器的老祖宗。
有了這個老祖宗,就可以編譯任意的C語言程式了,那是不是可以用C語言本身寫一個編譯器?只要用老祖宗編譯一下就可以了。
OK, 這麼一層層上來,終於得到了一個用C語言寫的編譯器, 真是夠麻煩的。
到這個時候,之前那個彙編寫的C語言編譯器就可以拋棄了。
當然,如果在C語言之前,已經出現了別的高階語言,例如Pascal,那就可以用Pascal來寫一個C語言的編譯器。
第一個Pascal的編譯器據說使用Fortran寫的。而做為第一個高階語言的Fortran,它的編譯器應該是組合語言寫的。
關於編譯器,這裡邊有個有趣的傳說:
傳說Unix 發明人之一的 Ken Thompson在貝爾實驗室,大搖大擺的走到任何一臺Unix機器前,輸入自己的使用者名稱和密碼,就能以root的方式登入!
貝爾實驗室人才濟濟,另外一些大牛發誓要把這個漏洞找出來,他們通讀了Unix的C原始碼,終於找到了登入的後門, 清理後門以後編譯Unix , 執行, 可是Thompson 還是能夠登入進去。
有人覺得可能是編譯器中有問題,在編譯Unix的時候植入了後門, 於是他們又用C語言重新寫了一個編譯器,用新的編譯器再次編譯了Unix, 這下總算天下太平了吧。
可是仍然不管用, Thompson 依然可以用root登入,真是讓人崩潰 !
後來Thompson 本人解開了祕密,是第一個C 語言編譯器有問題, 這個編譯器在編譯Unix原始碼的時候,當然會植入後門, 這還不夠,更牛的是,如果你用C 語言寫了一個新編譯器,肯定也需要編譯成二進位制程式碼啊,用什麼來編譯,只有用Thompson寫的那第一個編譯器來編譯,好了, 你寫的這個編譯器就會被汙染了,你的編譯器再去編譯Unix , 也會植入後門 :-)
說到這裡我就想起了幾年前的XcodeGhost 事件,簡單來說就是在Xcode(非官方渠道下載的)中植入了木馬,這樣XCode編譯出的ios app都被汙染了,這些app就可以被黑客利用做非法之事。
雖然這個XCodeGhost和Thompson的後面相比差得遠,但是提醒我們,下載軟體的時候要走正規渠道,從官方網站下載,認準網站的HTTPS標準,甚至可以驗證一下checksum。
3
可能有人問:我用匯編寫一段Hello World都很麻煩,居然有人可以用它寫複雜的編譯器?這可能嗎?
當然可能,在開發第一代Unix的時候,連C語言都沒有, Ken Thompson 和 Dennis Ritchie 可是用匯編一行行把Unix敲出來的。 WPS第一版是求伯君用匯編寫出來的, Turbo Pascal 的編譯器也是Anders 用匯編寫出來的,大神們的能力不是普通人能想象得到的。
對於編譯器來說,還可以採用“滾雪球”的方式來開發:
還是以C語言為例,第一個版本可以先選擇C語言的一個子集,例如只支援基本的資料型別,流程控制語句,函式呼叫...... 我們把這個子集稱為C0。
然後用匯編語言寫個編譯器,只搞定這個語言的子集C0,這樣寫起來就容易不少。
C0這個語言可以工作了,然後我們擴充套件這個子集,例如新增struct,指標...... ,把新的語言稱為C1。
那C1這個語言的編譯器由誰來寫? 自然是C0。
等到C1可以工作了,再次擴充套件語言特性,用C1寫編譯器,得到C2。
然後是C3, C4...... 最後得到完整的C語言。
這個過程被稱為bootstraping , 中文叫做自舉。