使用 C 和 C++ 的程式語言概念 / 語言處理器
在本章中,我們將研究不同型別的語言處理器,同時將使用稱為墓碑圖的圖形表示法。然而,不要因為大量使用這種符號而誤以為圖形表示本身具有任何意義。除非對它們所代表的底層概念有清晰的理解,否則隨意新增圖表來建立一些花哨的圖形毫無價值。
在我們對主題的論述中,將介紹基本概念、程式和機器,然後回顧語言處理技術、翻譯和解釋。在此基礎上,我們將探索更復雜的語言處理器使用方式,以構建更高效的環境。
從計算機科學家的角度來看,一個程式是一組規則模式,用於指導計算過程的演變。[1]它由特定符號系統中的符號表達式組成,該符號系統稱為程式語言。

一個機器是一個自動機 [當給定一個程式時],可以執行任務來簡化其受益者的生活。從計算機科學家的角度來看,術語機器是計算機或任何等效數學模型的同義詞。

程式只有以適當的機器程式碼形式表達才能在 [物理] 機器上執行。[2]

一個翻譯器是一個程式,它接受以一種語言(翻譯器的源語言)表達的任何文字,並生成以另一種語言(其目標語言)表達的語義等效文字。
特別是,一個編譯器從高階語言翻譯到低階語言(不僅僅是機器程式碼);一個彙編器從組合語言翻譯到相應的機器程式碼;一個反編譯器將低階語言程式翻譯成高階語言程式;一個反彙編器將機器程式碼程式翻譯成組合語言程式。

墓碑的頭部用箭頭分隔,命名了翻譯器的源語言 S 和目標語言 T。墓碑的底部命名了翻譯器的實現語言 L。

源程式和目標程式在語義上是等效的。[3]對源程式和目標程式使用相同的名稱是一種廣泛使用的約定,以強調這一事實。
一個交叉編譯器是一個在某臺機器(主機)上執行,但為另一臺不同機器(目標機)生成程式碼的編譯器。目標程式是在主機上生成的,但必須傳輸到目標機器上才能執行。這種傳輸通常稱為下載。

兩階段翻譯器是兩個翻譯器的組合。當您想將新的語言實現移植到不同的平臺時,這種方案很有用。您只需將新的語言翻譯成一種廣泛使用的程式語言,然後將該翻譯的輸出翻譯成機器程式碼。現在,您就在所有存在該無所不在的語言編譯器的平臺上擁有了程式語言實現。

更正式地說,我們可以說語義等效關係是等價關係。也就是說,它是自反的、對稱的和傳遞的。因此,我們可以輕鬆地將這個想法推廣到多個階段:一個 n 階段翻譯器是 n 個翻譯器的組合,涉及 n-1 箇中間語言。
翻譯器,無論是編譯器、彙編器還是任何型別的翻譯器,都只是另一段軟體。因此,與其他程式一樣,它本身也可以作為輸入被輸入到翻譯器中。(翻譯器唯一特別的地方,如果可以稱之為特別,就是它們將其他程式作為輸入。)

從下面的輸入和輸出圖中可以看出,翻譯器只是普通的程式

在翻譯中,必須將整個源程式翻譯成其目標程式等效程式,才能開始執行併產生結果。這可以比作翻譯一部小說:它一次性地全部翻譯完。
另一方面,一個直譯器是一個程式,它接受以特定語言(源語言)表達的任何程式(源程式),並立即執行該源程式。這種方法更像同聲傳譯員的工作方式:她在說話者發表宣告時進行翻譯;她不會等到他說完話才開始翻譯。
類似於微程式的取指-譯碼-執行迴圈,直譯器透過逐條取指、分析和執行源程式指令來工作。每次解釋一條原始碼指令時,它首先被取指,然後進行分析,包括將其翻譯成物理機器的指令,最後透過執行相應的機器程式碼指令來執行。
當以下情況成立時,解釋是有意義的
- 指令具有簡單的格式,可以輕鬆有效地進行分析。(請注意指令和指令格式之間的區別。)
- 指令很複雜;它們的執行需要很長時間,以至於用於取指和分析的時間可以忽略不計。
- 每條指令只執行一次(或者至少不經常執行)。
- 程式設計師在互動模式下工作,希望在輸入下一條指令之前看到每條指令的結果。
- 該程式僅使用一次後即被丟棄,因此執行速度並不重要。
對前兩項的推理可以透過分析在物理機器上執行原始碼指令的成本來理解。
(執行原始碼指令的成本)
執行原始碼指令的總成本等於所有階段成本的總和。執行階段成本(大致)等於執行相應機器語言指令的成本之和。請注意,每個原始碼指令對應多條機器語言指令,這是很自然的,因為源語言是更高層次的語言。
當原始碼指令格式簡單時, 和 太小,將被 掩蓋。也就是說,我們將有
總成本幾乎等於執行相應機器程式碼指令的成本。因此,由於解釋而損失的不多。
在複雜的原始碼指令的情況下, 太大,將支配指令的執行時間。因此,我們最終得到先前的結論:。
儘可能少地執行原始碼指令意味著由於獲取和分析階段而產生的成本不會重複支付。因此,仍然有理由將解釋視為一種替代方案。
第四項基本上是對解釋背後的想法的重新闡述:指令一次執行一條。解釋和互動性之間的這種並行關係體現在直譯器的最佳示例來自命令 shell 世界,例如 bash、csh 等。
最後一個專案的典型例子是應用程式的原型設計,其中開發了一個輕量級版本的應用程式,用於透過在開發過程的早期階段向客戶展示它來確保正確獲取使用者需求。由於這樣的應用程式只使用幾次,並且不需要很快,因此解釋被證明是一個不錯的選擇。
相反,當以下情況發生時,解釋不是一個明智的選擇:
- 該程式將在生產模式下執行,因此速度很重要。
- 指令被頻繁執行。例如,包含許多 for 迴圈的演算法不適合解釋。
- 指令格式複雜,因此分析起來很耗時。
| 問題 | ||
|---|---|---|
| 在實現矩陣操作模組時,我們應該選擇哪種型別的語言? | ||
|
| 問題 | ||
|---|---|---|
| 在實現一個自動執行多個程式協調執行的指令碼時,我們應該選擇哪種型別的語言? | ||
|
| 問題 | ||
|---|---|---|
| 在相容硬體上執行機器程式碼程式涉及哪種型別的處理? | ||
|
解釋程式
[edit | edit source]![]() |
![]() |
請注意,整個程式沒有進行翻譯;指令被一次一條地獲取、分析和執行,就像你在為 S 的抽象機器執行你的程式一樣。有關更多資訊,請參見有關抽象機器的部分。
真實機器和抽象機器
[edit | edit source]解釋可用於測試新設計的硬體,而無需實際構建它。這種方法透過縮短設計-構建-除錯週期為你節省了大量時間。請考慮以下典型場景。
- 設計人員提出硬體設計或修改以前構建的設計。
- 該設計既可以以軟體形式構建,也可以以硬體形式構建。但是,印刷電路板(即以硬體形式構建設計)不是您在辦公室就能做的事情;您需要找一些晶片製造公司來為您做。而且,如果您沒有大批次訂單,您一定會等很長時間。因此,硬體選項比較耗時,會損害您的競爭力。另一方面,軟體選項使您能夠透過在現有計算機上執行新設計機器的程式來測試您的設計,並且可以在您的辦公室完成。
- 在測試新硬體的過程中,可能會檢測到設計錯誤。如果是這樣,您需要回到第一步。否則,您可以進行下一步。
- 市場營銷人員開始進行銷售推介,將新產品推向市場,因為您已印刷了電路板。
這種解釋——即在另一臺機器上執行尚未構建的機器的程式——被稱為模擬。模擬器不能用於測量被模擬機器的絕對速度。但它仍然可以用來獲取有用的定量資訊,例如記憶體週期數、並行度等。
我們可以用高階程式語言(如 C)編寫直譯器。

該程式被進一步編譯成用機器程式碼 M 表達的另一個直譯器。

我們現在可以為新硬體編寫程式。

在直譯器之上執行程式在功能上等同於直接在機器上運行同一個程式。使用者在程式的輸入和輸出方面看到相同行為。這兩個過程甚至在細節上也很相似:直譯器以取指令-分析-執行迴圈工作,而機器以取指令-譯碼-執行迴圈工作。唯一的區別是直譯器是軟體製品,而機器是硬體製品(因此速度快得多)。
因此,機器可以被看作是硬體實現的直譯器。反之,直譯器可以被看作是軟體實現的機器。因此,直譯器有時被稱為抽象機器,以區別於其硬體對應物——稱為[實際]機器。所以,我們可以定義機器程式碼為一種存在硬體直譯器(至少在紙面上)的語言。
如果抽象機器和實際機器都實現了相同的語言 L,那麼它們在功能上是等效的。

解釋型編譯器
[edit | edit source]編譯器可能需要很長時間才能將源程式翻譯成機器程式碼,但目標程式將以全速執行。直譯器允許程式立即開始執行,但它會執行得很慢。
解釋型編譯器是編譯器和直譯器的組合,它兼具兩者的一些優點。關鍵思想是將源程式翻譯成中間語言,然後在執行中間語言程式的抽象機器上解釋翻譯結果。
中間語言設計為滿足以下要求
- 它的級別介於源語言和普通機器程式碼之間。
- 它的指令格式簡單,因此可以輕鬆快速地分析。
- 從源語言到中間語言的翻譯簡單快捷。
使用中間語言作為目標語言的兩個缺點是執行速度和反編譯的容易程度。前者意味著資源利用率低下,而後者意味著缺乏對產品保護。為了解決第二個缺點,可以使用名為混淆器的軟體,它會重新命名變數並重新排列程式碼,使其難以理解。在即時編譯器部分將詳細介紹如何提高執行速度。
使用解釋型編譯器最大的優點是目的碼的可移植性,這意味著您只需編譯一次程式即可在任何地方執行它。當您需要為使用不同架構的客戶提供服務時,這種方案非常有用。事實上,下面提供的兩個示例都具有這一點。
Pascal/P-code 解釋型編譯器
[edit | edit source]我們的第一個例子是作為編譯器套件的一部分使用,這使得 Pascal 在 70 年代後期和 80 年代初期成為學術界首選的程式語言。
Pascal/P-code 解釋型編譯器包含兩個元件

如果我們將 Pascal 程式輸入到翻譯器中,我們將獲得相應的 P-code 程式。

接下來,我們在 P-code 直譯器上執行生成的 P-code 程式。

現在,P-code 是一種以 Pascal 為中心的中間語言。這意味著它提供了強大的指令,這些指令直接對應於 Pascal 操作,例如陣列賦值、陣列索引和過程呼叫。因此,從 Pascal 到 P-code 的翻譯簡單快捷。儘管功能強大,但 P-code 指令具有像機器程式碼指令一樣的簡單格式,帶有操作碼和運算元欄位,因此易於分析。因此,P-code 解釋相對較快。
Java/位元組碼解釋型編譯器
[edit | edit source]我們的第二個例子是 Java 程式語言,它作為網際網路語言首次亮相——可移植性的終極測試實驗室。
與 Pascal/P-code 類似,Java/位元組碼解釋型編譯器包含兩個元件:Java 到位元組碼的編譯器和 Java 虛擬機器 (JVM)。除了充當位元組碼直譯器外,JVM 還提供安全、垃圾回收等服務。
看起來像之前的例子,不是嗎?嗯,除了名稱之外,它完全一樣。所以,Sun 並不是第一個發現可移植性的人!
考慮到Sun 的 picojava計劃,等效性可以進一步擴充套件,如下圖所示。

因此,Sun 的位元組碼在 Sun 的 picojava 上執行速度最快。(看起來是一個不錯的營銷技巧,嗯?)
問題:提供表示執行 C# 程式所需過程的圖表。
即時編譯器
[edit | edit source]上述方案的一個缺點是程式的執行時間。雖然它比純解釋快得多——因為解釋的是程式的較低階表示——但與機器級對應物的解釋相比,它很慢。透過在流程中引入即時編譯可以減輕這種負面影響。每當呼叫子程式時,直譯器——更準確地說,虛擬機器中稱為即時編譯器的部分——會動態地將子程式的中間程式碼表示編譯成其機器程式碼等效項並執行它。由於翻譯過程涉及的開銷,子程式的第一次呼叫將很昂貴。不過,後續呼叫將盡可能快。
事實上,即時編譯器(也稱為抖動器)生成的程式碼最終可能比原生程式碼編譯器生成的程式碼更快。這得益於抖動器中程式碼生成動態性的優勢。考慮將您的程式碼庫遷移到更高階的機器。由於虛擬機器及其抖動器(假設它們已更新以反映新機器的新功能)瞭解新機器的改進,因此可以利用這些改進,您所有的程式碼現在都執行得更快。但是,如果您有原生程式碼可執行檔案,情況並非如此。由於可執行檔案通常由針對特定機器的實現者建立,因此遷移到不同的機器將不會導致執行速度提高。[4][5]




