編譯到底做了什麼(***.c,編譯到底做.c
(第一次寫博客,好激動的說.......)
我們知道,一個程序由源代碼到可執行文件往往由這幾步構成:
預處理(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 (其實就是摘抄整理了一下,哈哈)