編譯器實現(一)


一 概論

1.基本概念 

 編譯器是將一種語言翻譯為另一種語言的計算機程序。

過程描述如下:

  源程序→ 編譯器→ 目標程序。

基礎知識:

  自動機原理

  數據結構

  離散數學

編譯器的發展:

馮諾依曼體系結構計算機 --> 機器語言程序 --> 匯編語言程序 --> FORTRAN語言及其編譯器/ Noam Chomsky自然語言研究 --> 優化技術(生成有效目標代碼)--> 編譯器的自動構造(分析程序生成器,如Yacc)/有窮自動機的研究 --> IDE整合

注釋:

  匯編語言不易編寫,依賴特定的機器。因此需要一種數學定義或自然語言的簡潔形式編寫程序操作。

  首先解決的是分析問題,用於限定上下文無關語言的識別的有效算法。對應喬姆斯基2型結構。具體解決方案有有窮自動機和正則表達式。

  然后深化了生成有效的目標代碼方法。到此以構成最初的編譯器。

  接下來解決的問題是編譯器的自動構造,分析程序生成器。

2.Chomsky hierarchy 喬姆斯基分類結構

參考:https://mjbin888.iteye.com/blog/1511207

     https://blog.csdn.net/Dongle_74/article/details/52745859

  Noam Chomsky的自然語言結構的研究,使得編譯器結構異常簡單,甚至還帶有了一些自動化。Chomsky的
研究導致了根據語言文法( grammar,指定其結構的規則)的難易程度以及識別它們所需的算法來為語言分類。

  喬姆斯基分類結構( Chomsky hierarchy)包括了文法的4個層次:0型、1型、2型和3型文法,且其中的每一個都是其前者的專門化。2型(或上下文無關文法( context-free grammar))被證明是程序設計語言中最有用的,而且今天它已代表着程序設計語言結構的標准方式。

終結符:語言的組成成分,是最后的內容
非終結符:不是語言的組成成分,而是在推到過程中的占位符,最終要替換終結符。

產生式:用終結符替代非終結符的規則。

 

  終結符,通俗的說就是不能單獨出現在推導式左邊的符號,也就是說終結符不能再進行推導。不是終結符的都是非終結符。非終結符可理解為一個可拆分元素,而終結符是不可拆分的最小元素。如:有α → β ,則α 必然是個非終結符。一般書上把非終結符用大寫字母表示,而終結符用小寫字母表示。識別符號就是開始符。由文法產生語言句子的基本思想是:從識別符號開始,把當前產生的符號串中的非終結符號替換為相應規則右部的符號串,直到最終全由終結符號組成。這種替換過程稱為推導或產生句子的過程,每一步成為直接推導或直接產生。
(非終結符:A,B,C,D,終結符:a,b,c,d)  
  0型文法,產生式左右部可以使用"非終結符"和"終結符"隨意組合,但左部不能為空,如DAaBb->CcdD;  
  1型文法,在0型文法的基礎上,要求右部的符號長度大於左部(空除外),如AaBb->CcddDd
  1<=|AaBb|<=|CcddDd|
  2型文法,在1型文法的基礎上,要求左部必須由非終結符號組成,如AB->CcdddDd  
  3型文法,在2型文法的基礎上,產生式必須型如:A->Aa|a或A->aA|a,比如:A->AA,A->aa,這些都不是
  3型文法也許是大家最難理解的,下面為大家舉幾個例子來說明:
    3型文法也叫正規文法,它對應於有限狀態自動機。它是在2型文法的基礎上滿足:A→α|αB(右線性)或   A→α|Bα(左線性)。
    如有:A->a,A->aB,B->a,B->cB,則符合3型文法的要求。但如果推導為:A->ab,A->aB,B->a,B->cB或推導  為:A->a,A->Ba,B->a,B->cB則不符合3型方法的要求了。具體的說,例子A->ab,A->aB,B->a,B->cB中的A->ab  不符合3型文法的定義,如果把后面的ab,改成“一個非終結符+一個終結符”的形式(即為aB)就對了。例子    A->a,A->Ba,B->a,B->cB中如果把B->cB改為B->Bc的形式就對了,因為A→α|αB(右線性)和A→α|Bα(左線   性)兩套規則不能同時出現在一個語法中,只能完全滿足其中的一個,才能算3型文法。
  注意:上面例子中的大寫字母表示的是非終結符,而小寫字母表示的是終結符

有窮自動機( finite automata)和正則表達式(regular expression)同上下文無關文法緊密
相關,它們與喬姆斯基的3型文法相對應。

 

3.與編譯器相關的程序

  1. 解釋程序
  2. 匯編程序
  3. 連接程序
  4. 裝入程序
  5. 預處理器
  6. 編輯器
  7. 調試程序
  8. 描述器
  9. 項目管理程序

4.翻譯步驟

(1) 掃描程序(scanner)

  在這個階段編譯器實際閱讀源程序(通常以字符流的形式表示)。掃描程序執行詞法分析(Lexical analysis):它將字符序列收集到稱作記號(token)的有意義單元中,記號同自然語言,如英語中的字詞相似。因此可以認為掃描程序執行與拼寫相似的任務。

(2) 語法分析程序(parser)
  語法分析程序從掃描程序中獲取記號形式的源代碼,並完成定義程序結構的語法分析(syntax analysis),這與自然語言中句子的語法分析類似。語法分析定義了程序的結構元素及其關系。通常將語法分析的結果表示為分析樹( parse tree)或語法樹(syntax tree)。

(3) 語義分析程序(semantic analyzer)
  程序的語義就是它的“意思”,它與語法或結構不同。程序的語義確定程序的運行,但是大多數的程序設計語言都具有在執行之前被確定而不易由語法表示和由分析程序分析的特征。這些特征被稱作靜態語義( static semantic),而語義分析程序的任務就是分析這樣的語義(程序的“動態”語義具有只有在程序執行時才能確定的特性,由於編譯器不能執行程序,所以它不能由編譯器來確定)。一般的程序設計語言的典型靜態語義包括聲明和類型檢查。由語義分析程序計算的額外信息(諸如數據類型)被稱為屬性( attribute),它們通常是作為注釋或“裝飾”增加到樹中(還可將屬性添加到符號表中)。

(4) 源代碼優化程序(source code optimizer)
  編譯器通常包括許多代碼改進或優化步驟。絕大多數最早的優化步驟是在語義分析之后完成的,而此時代碼改進可能只依賴於源代碼。這種可能性是通過將這一操作提供為編譯過程中的單獨階段指出的。每個編譯器不論在已完成的優化種類方面還是在優化階段的定位中都有很大的差異。

(5) 代碼生成器(code generator)
  代碼生成器得到中間代碼( I R),並生成目標機器的代碼。盡管大多數編譯器直接生成目標代碼,但是為了便於理解,本書用匯編語言來編寫目標代碼。正是在編譯的這個階段中,目標機器的特性成為了主要因素。當它存在於目標機器時,使用指令不僅是必須的而且數據的形式表示也起着重要的作用。例如,整型數據類型的變量和浮點數據類型的變量在存儲器中所占的字節數或字數也很重要。

(6) 目標代碼優化程序(target code optimizer)
  在這個階段中,編譯器嘗試着改進由代碼生成器生成的目標代碼。這種改進包括選擇編址模式以提高性能、將速度慢的指令更換成速度快的,以及刪除多余的操作。

 

5.編譯器中的主要數據結構

(1)記號

  當掃描程序將字符收集到一個記號中時,它通常是以符號表示這個記號;這也就是說,作為一個枚舉數據類型的值來表示源程序的記號集。

(2)語法樹

  如果分析程序確實生成了語法樹,它的構造通常為基於指針的標准結構,在進行分析時動態分配該結構,則整棵樹可作為一個指向根節點的單個變量保存。結構中的每一個節點都是一個記錄,它的域表示由分析程序和之后的語義分析程序收集的信息。

(3)符號表

  這個數據結構中的信息與標識符有關:函數、變量、常量以及數據類型。符號表幾乎與編譯器的所有階段交互:掃描程序、分析程序或將標識符輸入到表格中的語義分析程序;語義分析程序將增加數據類型和其他信息;優化階段和代碼生成階段也將利用由符號表提供的信息選出恰當的代碼。因為對符號表的訪問如此頻繁,所以插入、刪除和訪問操作都必須比常規操作更有效。盡管可以使用各種樹的結構,但雜湊表卻是達到這一要求的標准數據結構。有時在一個列表或棧中可使用若干個表格。

(4)常數表

  常數表的功能是存放在程序中用到的常量和字符串,因此快速插入和查找在常數表中也十分重要。但是,在其中卻無需刪除,這是因為它的數據全程應用於程序而且常量或字符串在該表中只出現一次。通過允許重復使用常量和字符串,常數表對於縮小程序在存儲器中的大小顯得非常重要。在代碼生成器中也需要常數表來構造用於常數和在目標代碼文件中輸入數據定義的符號地址。

(5)中間代碼

  根據中間代碼的類型(例如三元式代碼和P -代碼)和優化的類型,該代碼可以是文本串
的數組、臨時文本文件或是結構的連接列表。對於進行復雜優化的編譯器,應特別注意選擇允
許簡單重組的表示。

(6)臨時文件

  計算機過去一直未能在編譯器時將整個程序保留在存儲器中。這一問題已經通過使用臨時文件來保存翻譯時中間步驟的結果或通過“匆忙地”編譯(也就是只保留源程序早期部分的足夠信息用以處理翻譯)解決了。存儲器的限制現在也只是一個小問題了,現在可以將整個編譯單元放在存儲器之中,特別是在可以分別編譯的語言中時。但是偶爾還是會發現需要在某些運行步驟中生成中間文件。

 

6.編譯器結構中的其他問題

(1)分析和綜合

  將分析源程序以計算其特性的編譯器操作歸為編譯器的分析部分,而將生成翻譯代碼時所涉及到的操作稱作編譯器的綜合部分。當然,詞法分析、語法分析和語義分析均屬於分析部分,而代碼生成卻是綜合部分。在優化步驟中,分析和綜合都有。分析正趨向於易懂和更具有數學性,而綜合則要求更深的專業技術。因此,將分析步驟和綜合步驟兩者區分開來以便發生變化時互不影響是很有用的。

(2)前端和后端

  掃描程序、分析程序和語義分析程序是前端,代碼生成器是后端。但是一些優化分析可以依賴於目標語言,這樣就是屬於后端了,然而中間代碼的綜合卻經常與目標語言無關,因此也就屬於前端了。在理想情況下,編譯器被嚴格地分成這兩部分,而中間表示則作為其間的交流媒介。

  這一結構對於編譯器的可移植性十分重要,此時設計的編譯器既能改變源代碼(它涉及到重寫前端),又能改變目標代碼(它還涉及到重寫后端)。在實際中,這是很難做到的,而且稱作可移植的編譯器仍舊依賴於源語言和目標語言。

(3)遍

  編譯器發現,在生成代碼之前多次處理整個源程序很方便。這些重復就是遍( pass)。

(4)語言定義和編譯器

(5)編譯器的選項和界面

(6)出錯處理

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM