【編譯原理】代碼在編譯器中的完整處理過程


編譯器與解釋器

  • 編譯器:(相當於一次性翻譯完)
    程序設計語言是向人以及計算機描述計算過程的記號。但是,在一個程序可以運行之前,它首先需要被翻譯成一種能夠被計算機執行的形式。完成這項翻譯工作的軟件系統成為編譯器(Compiler)。
    簡單地說,一個編譯器就是一個程序,它可以閱讀以某一種語言(源語言)編寫的程序,並把程序翻譯成為一個等價的、用另一種語言(目標語言)編寫的程序。
    編譯器的重要任務之一是報告它在翻譯過程中發現的源程序中的錯誤。
    如果目標程序是一個可執行的機器語言程序,那么它就可以被用戶調用,處理輸入並產生輸出。

  • 解釋器:(相當於同聲傳譯
    解釋器(Interpreter)是另一種常見的語言處理器。它並不通過翻譯的方式生成目標程序。從用戶的角度看,解釋器直接利用用戶提供的輸入執行源程序中指定的操作。

在把用戶輸入映射成為輸出的過程中,由一個編譯器產生的機器語言目標程序通常比一個解釋器快很多。然而,解釋器的錯誤診斷效果通常比編譯器更好,因為它逐個語句地執行源程序。

語言處理過程

如上圖所示,除了編譯器之外,創建一個可執行的目標程序還需要一些其他程序。

  1. 一個源程序可能被分割成為多個模塊,並存放於獨立的文件中,把源文件聚合在一起的任務有時會由一個被稱為預處理器(Preprocessor)的程序獨立完成。預處理器還負責把那些稱為宏的縮寫形式轉換為源語言的語句、刪除注釋等。
  2. 然后,將經過預處理的源程序作為輸入傳遞給一個編譯器(Compiler)。編譯器可能產生生一個匯編語言程序作為其輸出,因為匯編語言比較容易輸出和調試。
  3. 接着,這個匯編語言程序由稱為匯編器(Assembler)的程序進行處理,並生成可重定位的機器代碼。
  4. 大型程序經常被分成多個部分進行編譯,因此,可重定位的機器代碼有必要和其他可重定位的目標文件以及庫文件連接到一起,形成真正在機器上運行的代碼。一個文件的代碼可能指向另一個文件中的位置,而鏈接器(Linker)能夠解決外部內存地址的問題。
  5. 最后加載器(Loader)把所有的可執行目標文件放到內存中執行。

語言處理實例

由上面我們可以了解到語言處理的整個過程,那么放到實際的例子當中是怎么處理的呢?
下面我們以C語言打印Hello,World!為例子。

預處理→編譯→匯編→鏈接

  1. 預處理階段(.c→.i):編譯器將C程序的頭文件編譯進來,還有宏的替換、刪除注釋等。

    科普:頭文件作為一種包含功能函數、數據接口聲明的載體文件,主要用於保存程序的聲明,而定義文件用於保存程序的實現。

  2. 編譯(.i→.s):轉換為匯編語言文件:這個階段編譯器主要做詞法分析、語法分析、語義分析等,在檢查無錯誤后,把代碼翻譯成匯編語言。
  3. 匯編階段(.s→.o)得到機器語言:匯編器將hello.s翻譯成機器語言保存在hello.o中(二進制文本形式)。
  4. 鏈接階段(.s→.exe):printf函數存在於一個名為printf.o的單獨預編譯目標文件中。必須得將其並入到hello.o的程序中,鏈接器就是負責處理這兩個的並入,結果得到hello.exe文件,他就是一個可執行的目標文件。

編譯器編譯的過程

我們把編譯器看作一個黑盒子,它能夠把源程序映射為在語義上等價的目標程序。如果把黑盒子稍微打開一點,我們就會看到這個映射過程由兩個部分組成:前端部分后端部分

  • 分析(analysis)部分:通常被稱為編譯器的前端(front end),它把源程序分解成為多個組成要素,並在這些要素之上加上語法結構。然后,它使用這個結構來創建該源程序的一個中間表示。如果分析部分檢查出源程序沒有按照正確的語法構成,或者語義上不一致,它就必須提供有用的信息,使得用戶可以按此進行改正。分析部分還會收集有關源程序的信息,並把信息存放在一個成為符號表(symbol table)的數據結構中。符號表將和中間表示形式一起傳送給綜合部分。

  • 綜合(synthesis)部分:通常被稱為編譯器的后端(back end),它根據中間表示和符號表中的信息來構造用戶期待的目標程序。
    • 詞法分析器:(也稱為掃描器)詞法分析是編譯過程的基礎,任務是掃描源程序,根據語言的詞法規則分解和識別出每個單詞,並把單詞翻譯成相應的機內表示。在識別單詞的過程中同時也做詞法檢查。
    • 語法分析器:(有時簡稱為分析器)語法分析是在詞法分析的基礎上進行的。任務是根據語言的語法規則把單詞符號流分解成格內語法單位,如 表達式、語句等。通過語法分析確定整個輸入符號流是否構成一個語法正確的程序。
    • 語義分析器:任務是對源程序進行語義檢查,其目的是保證標識符和常數的正常使用,把必要的信息收集保存到符號表或中間代碼程序中,並進行相應的處理。
    • 中間代碼生成器:必不可少的階段,任務是在語法分析和語義分析基礎上把語法成分的語義對其繼續翻譯,翻譯的結果是某種中間代碼形式,這種中間代碼的結果簡單,接近計算機的指令形式,能夠很容易被翻澤成計算機指令,常用的中間代碼有三元式、四元式和逆波蘭式。
    • 代碼優化器:對程序代碼進行等價(即 不改變程字的運行中間表示形式結果)變換。程序代碼可以是中間代碼(如 四元式代碼),也可以是目標代碼。等價的含義是使得交換后的代碼運行結果與變換前代碼運行結果相同,優化的含義是使最終生成的目標代碼短(運行時間更知、占用空間更小),時空效率優化。原則上,優化可以在編譯的各個階段進行,但最主要的一類是對中間代碼進行優化,這類優化不依賴於具體的計算機。
    • 目標代碼生成器:將中間代碼或優化之后的中間代碼轉換為等價的目標代碼,即 機器指令或匯編語言。由中間代碼很容易生成目標程序(地址指令序列),這部分工作與機器關系密切,所以要根據機器進行。在做這部分工作時(要注意充分利用累加器),也可以進行優化處理。
    • 表格管理:編譯過程中源程序的各種信息被保留在種種不同的表格,編譯各階段的工作都涉及到構造、查找、或更新有關的表格。編譯程序的公共輔助部分,對源程序中的各種量進行管理,登記在相應的表格。編譯程序處理時通過查表得到所需的信息。

      還記得Debug調試的時候觀察變量值的變化的那個表格嗎?

    • 出錯處理:如果編譯過程中發現源程序有錯誤,編譯程序應報告錯誤的性質和錯誤的發生的地點,並且將錯誤所造成的影響限制在盡可能小的范圍內,使得源程序的其余部分能繼續被編譯下去,有些編譯程序還能自動糾正錯誤,這些工作由錯誤處理程序完成。需要注意的是,一般編譯器只做語法檢查和最簡單的語義檢查,而不檢查程序的邏輯。

詞法分析器

詞法分析器的任務是將程序的字符流轉換到記號流。
字符流:被編譯的語言例如C、JAVA、ASCII、Inicode.
記號流:編譯器內部定義的數據結構,編碼所識別出的詞法單元。

例如讀入一個程序語句:

if (x>0)
    y="NEMO";   //詞法分析器設置成自動忽略空格,以下空格用\x20表示

詞法分析器依次讀入:i、f、\x20. (、x、)、0、)、\n、y、=、"NEMO"、;。
進行分析后得到:

IF LPARE IDENT(x) GT INT (0) RPAREN
    IDENT(y) ASSIGN STRING ( "NEMO" ) SEMICOLON EOF

我們對這些“詞語記號“進行數據結構定義:

enum type {IF. LPAREN. ID. INLIT,...}
struct token {
enum type k;
char *lexeme;   //單詞
};  //例如if (x>0), 我們就可以編程為: token{k= IF, lexeme =0};
token{k= LPAREN. lexeme=0}; token{k = ID, lexeme= “x”};

目前至少有兩種實現方案:

  • 手工編碼實現法(相對復雜,且容易出錯目前非常流行的GCC、LLVM 等);
  • 詞法分析器的生成器(自動的,可以快速原型、代碼量較少,但是控制細節難)

語法分析器

語法分析器的主要任務是對詞法分析的輸出結果記號流單詞序列進行分析,識別合法的語法單元並將其轉換輸出為下一階段可以識別的語法樹。

實現方法:

  • 手工方式:遞歸下降分析器
  • 使用語法分析器的自動生成著: LL(1)、 LR(1)

兩種方式在實際的編譯器中都有廣泛的應用,其中自動的方式更適合快速對系統進行原型開發,手工的方式更適台對系統進行調優。

語義分析器

語法樹(分析樹)是句子結構的圖形表示,它代表了句子的推導結果,有利於理解句子語法結構的層次。簡單來說,語法樹就是按照某一規則進行推導時所形成的樹。和推導所用的順序無關(最左、最右、其他)
特點:

  • 樹中每個內部節點代表非終結符
  • 每個葉子節點代表終結符
  • 每一步推導代表如何從雙親結點生成它的直接孩子節點

二義性:若對於一個文法的某一句子存在兩顆不同的語法樹,則該文法是二義性文法;否則是無二義性文法。(包含二義性的句子)
從編譯器角度,二義性文法存在的問題:
同一個程序會有不同的含義,因此程序運行的結果不是唯一的。
解決方法:文法的重寫


免責聲明!

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



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