天天看點

編譯器"自舉與移植"原理

        本文基于對《編譯原理與實踐》中有關編譯器自舉與移植部分的讀書“筆記”形式,因為原書是老外寫的,感覺翻譯的地方好多語句不通或難以了解,是以花了好多功夫研究這一塊。注:本文中與原書一緻的地方都是PDF截圖。

編譯器"自舉與移植"原理

         這段話的意思是說,最開始的時候,沒有任何編譯器,也就是說即使有進階語言,比如C語言,也是不能編譯的,更不用說運作了。是以最開始的編譯器是用彙編(或機器語言)寫的。而且編譯器存在的形式必須是機器碼形式--隻有機器碼才能得到機器的執行,進而能夠将“進階語言”作為輸入,将其翻譯為機器碼。但是,現在有了進階語言和對應的編譯器,我們就可以采用這種進階語言去編寫新的語言(或者就是這種語言本身)的編譯器了--隻要用這種語言,比如C語言,去寫完編譯器的源代碼,然後用編譯器将其翻譯為本地機器碼,就可以了。比如,現在機器A上隻有C語言編譯器,我們想開發一種新的語言Java及其編譯器,我們可以用C語言去完成java編譯器的編寫工作,然後将其用C語言編譯器便以為本地可執行機器碼,我們就得到了java編譯器。這裡,還要說一下,為什麼C語言寫的java編譯器可以處理java源檔案呢?其實,無論是什麼語言的源檔案,比如C語言的源檔案xx.c,C++的源檔案xx.cpp,還是java的源檔案xx.java...它們本質上都是具有一定格式和順序的文本檔案,再說的本質點,它們在計算機内都是010101式的字元流,再再本質一點,它們就是計算機記憶體中的高低電平。但還是了解為普通的文本檔案就可以了。而編譯器呢?當其作為機器碼在本地運作時,這些源檔案就是作為編譯程式的輸入,不管是哪種語言的源檔案,都可以被任意的編譯程式作為輸入,但是特定語言的編譯器隻認識和編譯具有特定文法格式的源檔案,也就是說,C語言編譯器的輸入源檔案可以是任意的文本,但是他隻認識和編譯具有C語言文法格式的文本。。。因為這種編譯器隻是為這種語言設計和實作的。

編譯器"自舉與移植"原理

        以上這段話,指出了另一個存在的問題---當我想在另一種機器B(不同于機器A)上産生java編譯器,但機器B上還沒有C編譯器的存在,我們就需要産生一個交叉編譯器:在這裡就是一個在機器A上運作,但最終翻譯得到的不是機器A的機器碼而是機器B的機器碼的一種編譯器,這樣,由這個所謂的“交叉編譯器”編譯原本用C語言完成的java編譯器,得到的機器碼copy到機器B上就得到了可以在機器B上運作的java編譯器。

        之後,原文中為了解釋“交叉編譯器”的原理,介紹了T型圖,在我的筆記中,将上文中T型圖整體稱為編譯器體系,S部分稱為編譯器的輸入,H部分稱為編譯器的資料進行中心,其實作語言為執行語言,T部分稱為編譯器的輸出。即T型圖抽象了一個編譯器系統,有輸入,有處理,也有輸出。簡單的舉兩個T型圖的執行個體:1.C語言編譯系統--輸入為C語言源檔案xx.c,進行中心為本地編譯器的可執行機器碼,輸出為最終翻譯得到的本地可執行機器碼形式。2.還是C語言編譯系統,隻不過輸出部分有所變化--将輸出變為了彙程式設計式的形式,然後再用另一個“編譯系統”将此彙程式設計式檔案作為輸入,産生最終的可執行機器碼。

編譯器"自舉與移植"原理

        以上這一段介紹了T型圖的一種組合方式,我覺得可以稱為橫向水準組合,本質上就是将一個編譯系統的輸出B作為另一個編譯系統的輸入(前提是前者的輸入可以作為後者的輸出,至于兩個編譯器是否一定要在同一台機器或者同一類型的機器上運作,我覺得是不必要的)。在上文我的筆記中介紹T型圖的時候,舉得第二個例子就是對應這種情況。

編譯器"自舉與移植"原理

        編譯系統“T型圖”的另一種組合方式--我覺得有點“交叉”的感覺。這種方式是借助了另一個編譯系統(就是圖中以M為進行中心的T型圖)--我覺得這就是所謂的交叉編譯器(也就是原文中“由機器H到機器K的編譯器”,至于原文中的後半句“翻譯由H到K的其他編譯器的執行語言”,感覺讀不懂,但我覺得原文如果是“翻譯由H到K的其他機器的執行語言”,但“機器”和“編譯器”從某種層次上其實是一個意思。)。原文可以改寫成“利用‘機器’H到‘機器’K的編譯器M(T型圖)來翻譯由H到K的執行語言“,我局的意思就是說用M将H翻譯為K。進而得到一個由K語言編寫的能将A翻譯成B的編譯器。本質上,這種組合就是利用交叉編譯器,将已經存在的一種編譯系統T型圖中的進行中心的執行語言轉化為另一種執行語言--重點是,偷天換日,将編譯系統的執行語言換了。

編譯器"自舉與移植"原理

        這段話也是難以了解。先來了解”在機器H上利用B語言現存的編譯器“,”在機器H上“--說明本段建立在這樣的一個事實之上--隻存在一種機器H。這也就奠定了圖中兩個編譯器的運作環境,就是這台機器,也就是說其執行語言最終是機器碼,沒有這種以機器碼為執行語言的編譯器,将是不可直接運作的,更談不上編譯了。是以”在機器H上利用B語言現存的編譯器“,就是說這台機器上存在以機器碼為執行語言的B的編譯器。圖中第一個T型圖所表示的編譯系統在機器H上是無法直接運作,因為其執行語言是B語言而不是機器語言。是以要利用圖中第二個T型圖表示的編譯系統,将B語言翻譯為本地機器碼,進而得到一個A語言的以本地機器碼為執行語言的編譯器。原文中 ”将語言A翻譯為用B語言編寫的語言H“,這句話真的是難以讀懂。我覺得這句話有誤,略過,也不影響本文的分析過程和完整性。上圖所示過程的本質是,利用一種以本地機器碼為執行語言的編譯系統,将一個以非本地機器碼(在這裡就是進階語言,比如彙編語言,C語言)為執行語言的編譯系統(這種編譯系統在機器上是不能獨立和直接運作的,因為機器隻能識别和執行本地機器碼)的執行語言翻譯為本地執行語言機器碼,上圖中就是将第一個編譯系統的執行語言B替換為了本地執行語言H。進而可以達到原編譯系統依靠B語言可以直接工作的假象。

        舉個實際的例子,假設存在一種語言A的編譯器,可以将語言A翻譯為語言H--假設這樣的一種編譯系統:輸入為語言A,編譯器進行翻譯處理,然後得到輸出語言H。我們以C語言實作了這種語言A的編譯器。假設編譯器的源檔案我們寫好了,叫做A.c,現在,有一個文本檔案A.txt,理論上,經過A.c的”翻譯“,可得到輸出檔案H.txt。這就構成了上圖中的第一個T型圖所表示的編譯系統,輸入檔案為A.txt,編譯器的執行語言為C語言(就是A.c的内容,C語言的源代碼形式),輸出檔案H.txt。但是,這個編譯系統是不能直接運作和編譯的--A.c得不到機器的直接執行,因為機器隻識别和運作本地機器碼(雖然A.c檔案本質上和機器碼在存儲形式上沒有任何差別,都是01序列,但是他們的形式和格式不一樣)。是以,要利用機器上已經存在的C語言的編譯器(其執行語言為本地機器碼的編譯系統),将A.c翻譯成本地機器碼語言的形式A.h,進而A.h可以在機器上直接運作,并以A.txt作為輸入,将其翻譯為H.txt。

編譯器"自舉與移植"原理

        這第二個假定不同于第一個假定之處在于,存在兩種不同的機器。這就引出了交叉編譯器的概念,交叉編譯器就是一個能生成不同于它所運作的機器之上的目标代碼的編譯器。說白了就是在Intel機器上運作并生成另一個機器Arm上的目标代碼。關于交叉編譯器,最後再細講。

編譯器"自舉與移植"原理

        這段話,這麼解釋,比如,用C語言編寫C語言的編譯器,這種方法是很普遍的。但這會變現一個循環錯誤,後文也提到了,如果源語言的編譯器不存在,那麼編譯器本身也就不可能被編譯了,解決的方法就是用機器碼或彙編産生一個簡單的C語言編譯器。見下圖:

編譯器"自舉與移植"原理
編譯器"自舉與移植"原理

        以上内容較為易于了解,就不多說了。

編譯器"自舉與移植"原理

        這裡所說的源代碼後端,指的應該是比如用C語言寫的C語言編譯器的源代碼,隻要将源代碼中的”後端“修改為生成指定的機器代碼,然後用舊的編譯器編譯就可以得到一個交叉編譯器,因為此編譯器和舊編譯器不同,雖然都運作在同一台機器上,但新的編譯器生成另一種機器的目标代碼。進而,用此新的編譯器編譯修改”後端“後的編譯器源代碼,可以得到一個可以在另一台機器上運作的編譯器。

繼續閱讀