(第一次寫部落格,好激動的說.......)
我們知道,一個程式由源代碼到可執行檔案往往由這幾步構成:
預處理(Prepressing)-> 編譯(Compilation)-> 彙編(Assembly)-> 連結(Linking)。
編譯過程就是把預處理完的檔案進行一系列詞法分析、文法分析、語義分析及優化後生産相應的彙編代碼檔案,這個過程往往是我們所說的整個程式建構的核心部分。那麼,這個核心部分究竟做了什麼呢。
各位看官容我挽起袖子,且聽我娓娓道來。
編譯器做了什麼?
從最直覺的角度來說,編譯器就是将進階語言翻譯成機器語言的一個工具。
以 C語言為例,解釋一下 ***.c -> ***.o 的過程。 假設test.c有下面一段代碼
array[index] = (index + 4) * (2 + 6);
下面就來談談這個表達式是如何翻譯成機器語言的過程。
這個過程主要有如下五步,看起來好長的樣子,看官需靜下心來慢慢看。。。。
1.詞法分析 -- 将源代碼字元序列分割成一系列的記号
源代碼程式被輸入到掃描器(Scanner)。
掃描器的任務就是:運用一種有限狀态機(Finite State Machine)的算法,将源代碼字元序列分割成一系列的記号(Token)。還有一些其他工作(将辨別符放到符号表,将數字、字元串放到文字表中)
如下圖(因為表格換頁了,是以拍出來是這個樣子,望海涵)

詞法分析産生的記号可以分為如下幾類:關鍵字、辨別符、字面量(包括數字、字元串等)和特殊符号(+ - * /)。
需要注意的是:C語言的宏替換和檔案包含等工作一般不是編譯器做的,而是交給一個獨立的預處理器。
有一個叫做lex的程式可以實作詞法掃描。
2.文法分析 -- 産生文法樹(以表達式為節點的樹)
文法分析器(Grammar Parser)将對上面産生的記号進行文法分析,産生文法樹(Syntax Tree)-- 采用的是上下文無關文法的分析手段。
簡單的說,文法分析器生成的文法樹就是以表達式(Expression)為節點的樹。
如圖
文法分析階段必須對好多東西(符号的含義和優先級)進行區分,若出現了不合法(如括号不比對,表達式缺少操作符等),編譯器就會報告文法分析階段的錯誤。
僅僅是完成了對表達式文法層面的分析,并不了解這個語句是否真正有意義。
文法分析也有一個現成的工具叫yacc(Yet Another Compiler Compiler)。
3.語義分析 -- 将文法樹中節點标明含義
接下來就是,由語義分析器(Semantic Analyzer)來完成。
任務就是:為文法樹的表達式辨別類型。就是下面這個樣子,多了類型
如圖
符号和數字是最小的表達式。
編譯器所能分析的語義是靜态語義。(動态語義不能被分析)
靜态語義:在編譯階段可以确定的語義,通常包括聲明和類型的比對,類型的轉換。
動态語義:在運作期才能确定的語義,比如将0作為除數是一個運作期語義錯誤。
4.中間語言生成 -- 一個優化過程
現代的編譯器有着很多層次的優化,這裡介紹的是一個源碼級優化器(Source Code Optimizer),會在源碼級别進行優化。比如例子中的(2 + 6),因為在編譯階段可以确定為8,是以這個表達式被優化掉了。
因為直接在文法樹上做優化是比較困難的,是以源代碼優化器往往将整個文法樹轉換成中間代碼(Intermediate Code),就是文法樹的順序表示(已經非常接近目标代碼了)。
中間代碼有很多類型,在不同的編譯器有着不同的表現形式,常見的有:三位址碼(Three-address Code)、P代碼(p-Code)。
中間代碼使得編譯器可以分成前端和後端。
前端:負責産生機器無關的中間代碼
後端:将中間代碼轉換成目标代碼
5.目标代碼生成與優化(這裡開始就是後端了,前面都是前端)
編譯器後端主要包括:代碼生成器(Code Generator)和目标代碼優化器(Target Code Optimizer)。
代碼生成器:将中間代碼轉換成目标機器代碼。這個過程非常依賴于機器,因為不同的機器有不同的字長,寄存器,整數資料類型和浮點數資料類型等。
對于我們的例子,可能會生成下面的代碼序列(用x86的彙編來表示),如圖
目标代碼優化器:對上述的目标代碼進行優化。比如:選擇合适的尋址方式,使用位移來代替乘法運算,删除多餘的指令等。
對于我們的例子,有可能會優化成這個樣子。
如圖。
------ 我是分割線 ------
好了,忙活了這麼久,源代碼終于變成了目标代碼。
這時候問題來了,index和array的位址還沒有确定。若用把目标代碼用彙編器編譯成真正能在機器上執行的指令,這兩個位址從何而來呢。
若index和array定義在跟上面的源代碼同一個編譯單元裡,那麼編譯器可以為它們配置設定空間,确定它們的位址。
若定義在其他子產品呢?說來就話長了。。。。。。
附在那本書的一些話:(助于了解)
(1).現代的編譯器可以将一個源代碼檔案編譯成一個未連結的目标檔案,然後由連結器最終将這些目标檔案連結起來形成可執行檔案。
(2).彙編器是将彙編代碼轉變成機器可以執行的指令,每一個彙編語句幾乎都對應一條機器指令。
(3).是以彙編器的彙編過程相對于編譯器來講比較簡單,它沒有複雜的文法,也沒有語義,也不需要做指令優化,隻是根據彙編指令和機器指令的對照表一一翻譯就可以了。
(4).經過預編譯、編譯和彙編直接輸出目标檔案(Object File)。
參考文獻《程式員的自我修養--連結、裝載與庫》 P41-P48 (其實就是摘抄整理了一下,哈哈)