編譯器構造/簡介
一個 **編譯器** 是一種計算機程式,它實現一種程式設計語言規範來“翻譯”程式,通常是一組構成以 *源語言* 編寫的 *原始碼* 的檔案,將其轉換為等效的機器可讀指令 **(目標語言,通常具有稱為目的碼的二進位制形式)**。這種翻譯過程稱為 *編譯*。我們 *編譯* 源程式以建立 *編譯後的程式*。編譯後的程式隨後可以執行(或執行)以執行原始源程式中指定的操作。
**源語言** 與機器程式碼相比,始終是一種更高級別的語言,使用英語單詞和數學符號的混合編寫,組合語言是最低階的可編譯語言(*彙編器* 是編譯器的一種特殊情況,將 *組合語言* 翻譯成機器程式碼)。更高級別的語言在編譯器/直譯器中是最複雜的,不僅因為它們增加了原始碼與生成的機器程式碼之間的抽象級別,而且因為需要更高的複雜性來形式化這些抽象結構。
**目標語言** 通常是一種低階語言,例如組合語言,使用機器指令的加密縮寫編寫,在這種情況下,它還會執行彙編器來生成最終的機器程式碼。但有些編譯器可以直接為某些實際或虛擬計算機生成機器程式碼,例如為 Java 虛擬機器生成的位元組碼。
另一種常見的編譯結果方法是針對 *虛擬機器*。它將進行即時編譯和位元組碼解釋,並將傳統的編譯器和直譯器分類模糊化。
例如,C 和 C++ 通常會為目標 `架構` 編譯。缺點是由於存在多種型別的處理器,因此需要進行許多不同的編譯。相比之下,Java 將針對 Java 虛擬機器,Java 虛擬機器是 `架構` 上方的一個獨立層。區別在於生成的位元組碼,而不是真正的機器程式碼,帶來了可移植性的可能性,但需要為每個平臺提供一個 Java 虛擬機器(位元組碼直譯器)。這個位元組碼直譯器的額外開銷意味著執行速度更慢。
一個 **直譯器** 是一種計算機程式,它在執行時執行源程式的翻譯。它不會生成獨立的可執行程式,也不會生成可包含在其他程式中的目標庫。
進行大量計算或內部資料操作的程式,通常在編譯形式下執行速度比解釋時快。但進行大量輸入/輸出和很少計算或資料操作的程式,在這兩種情況下可能以大致相同的速度執行。
編譯器和直譯器本身都是計算機程式,必須用某種 *實現語言* 編寫。直到 20 世紀 70 年代初,大多數編譯器都是用某種特定型別計算機的組合語言編寫的。C 和 Pascal 編譯器的出現,每個編譯器都用自己的源語言編寫,導致更普遍地使用高階語言來編寫編譯器。如今,作業系統至少會為使用者提供一個免費的 C 編譯器,有些作業系統甚至將它包含在作業系統分發中。
編譯器構造通常被認為是一項高階程式設計任務,而不是新手任務,主要是因為需要大量的程式碼(以及理解這些程式碼的難度),而不是任何特定編碼結構的難度。對此,大多數關於編譯器的書籍都有一些責備。生產編譯器和教育練習之間的巨大差距加劇了這種悲觀態度。
對於用其自己的源語言編寫的第一個版本的編譯器,你有一個 引導 問題。一旦你讓一個簡單的版本工作,你就可以用它來改進它本身。
- 編譯過程
在最高級別,編譯被分成多個部分
- 詞法分析(標記化)
- 語法分析(解析)
- 型別檢查
- 程式碼生成
任何編譯器都有一些基本需求,這些需求可能比大多數程式更嚴格
- 任何有效的程式都必須被正確地翻譯,即不允許錯誤翻譯。
- 任何無效的程式都必須被拒絕,不能翻譯。
不可避免地,有一些有效的程式由於其大小或複雜性(相對於可用硬體)而無法被翻譯,例如由於記憶體大小而產生的問題。編譯器還可能有一些固定大小的表,這些表限制了可以編譯的內容(一些語言定義對某些表的尺寸設定了明確的下限,以確保可以編譯合理大小/複雜性的程式)。
還有一些理想的需求,其中一些可能是相互排斥的
- 錯誤應該用源語言或程式來報告。
- 應該指出檢測到錯誤的位置;如果實際錯誤可能發生在更早的時間,那麼也應該提供一些關於可能原因的指示。
- 編譯應該很快。
- 翻譯後的程式應該很快。
- 翻譯後的程式應該很小。
- 如果源語言有一些國家或國際標準
- 理想情況下,應該實現整個標準。
- 任何限制都應該記錄清楚。
- 如果已經實現了對標準的擴充套件
- 這些擴充套件應該記錄為擴充套件。
- 應該有一種方法可以關閉這些擴充套件。
還有一些可能引起爭議的需求需要考慮(參見關於 處理錯誤 的章節)
- 在翻譯後的程式執行時檢測到的錯誤仍然應該相對於原始源程式進行報告,例如行號。
- 在翻譯後的程式執行時檢測到的錯誤應該包括除以 0、記憶體不足、使用過大或過小的陣列下標/索引、嘗試使用未定義的變數、指標使用不當等。
為了便於解釋,我們將編譯器分為前端和後端。它們甚至不需要用相同的實現語言編寫,只要它們可以透過某種中間表示有效地通訊即可。
以下列表詳細說明了前端和後端執行的任務。請注意,這些任務不是按照下面概述的順序執行的,並在後面的章節中進行了更詳細的討論。
- 前端
- 詞法分析 - 將字元轉換為標記
- 語法分析 - 檢查標記的有效序列
- 語義分析 - 檢查含義
- 一些全域性/高階最佳化
- 後端
- 一些本地最佳化
- 暫存器分配
- 窺孔最佳化
- 程式碼生成
- 指令排程
幾乎所有源語言方面的處理都由前端完成。可以為不同的高階語言使用不同的前端,以及一個共同的後端,它執行大部分最佳化。
幾乎所有與機器相關的方面都由後端處理。可以為不同的計算機使用不同的後端,以便編譯器可以為不同的計算機生成程式碼。
前端通常由語法分析處理控制。在必要時,語法分析程式碼將呼叫一個執行詞法分析並返回下一個標記的例程。在語法分析過程中的選定點,將呼叫適當的語義例程,這些例程執行任何相關的語義檢查和/或將資訊新增到內部表示中。