天天看點

編譯到底做了什麼(***.c -> ***.o的過程)

 (第一次寫部落格,好激動的說.......)

我們知道,一個程式由源代碼到可執行檔案往往由這幾步構成:

預處理(Prepressing)-> 編譯(Compilation)-> 彙編(Assembly)-> 連結(Linking)。

編譯過程就是把預處理完的檔案進行一系列詞法分析、文法分析、語義分析及優化後生産相應的彙編代碼檔案,這個過程往往是我們所說的整個程式建構的核心部分。那麼,這個核心部分究竟做了什麼呢。

各位看官容我挽起袖子,且聽我娓娓道來。

編譯器做了什麼?

  從最直覺的角度來說,編譯器就是将進階語言翻譯成機器語言的一個工具。

  以 C語言為例,解釋一下 ***.c -> ***.o 的過程。 假設test.c有下面一段代碼

  array[index] = (index + 4) * (2 + 6);

  下面就來談談這個表達式是如何翻譯成機器語言的過程。

  這個過程主要有如下五步,看起來好長的樣子,看官需靜下心來慢慢看。。。。

1.詞法分析 -- 将源代碼字元序列分割成一系列的記号

  源代碼程式被輸入到掃描器(Scanner)。

  掃描器的任務就是:運用一種有限狀态機(Finite State Machine)的算法,将源代碼字元序列分割成一系列的記号(Token)。還有一些其他工作(将辨別符放到符号表,将數字、字元串放到文字表中)

  如下圖(因為表格換頁了,是以拍出來是這個樣子,望海涵)

編譯到底做了什麼(***.c -> ***.o的過程)
編譯到底做了什麼(***.c -> ***.o的過程)

  詞法分析産生的記号可以分為如下幾類:關鍵字、辨別符、字面量(包括數字、字元串等)和特殊符号(+ - * /)。

  需要注意的是:C語言的宏替換和檔案包含等工作一般不是編譯器做的,而是交給一個獨立的預處理器。

  有一個叫做lex的程式可以實作詞法掃描。

2.文法分析 --  産生文法樹(以表達式為節點的樹)

  文法分析器(Grammar Parser)将對上面産生的記号進行文法分析,産生文法樹(Syntax Tree)-- 采用的是上下文無關文法的分析手段。

 簡單的說,文法分析器生成的文法樹就是以表達式(Expression)為節點的樹。

 如圖

編譯到底做了什麼(***.c -> ***.o的過程)

 文法分析階段必須對好多東西(符号的含義和優先級)進行區分,若出現了不合法(如括号不比對,表達式缺少操作符等),編譯器就會報告文法分析階段的錯誤。

 僅僅是完成了對表達式文法層面的分析,并不了解這個語句是否真正有意義。

 文法分析也有一個現成的工具叫yacc(Yet Another Compiler Compiler)。

3.語義分析  --  将文法樹中節點标明含義

  接下來就是,由語義分析器(Semantic Analyzer)來完成。

  任務就是:為文法樹的表達式辨別類型。就是下面這個樣子,多了類型

  如圖

編譯到底做了什麼(***.c -> ***.o的過程)

 符号和數字是最小的表達式。

  編譯器所能分析的語義是靜态語義。(動态語義不能被分析)

  靜态語義:在編譯階段可以确定的語義,通常包括聲明和類型的比對,類型的轉換。

  動态語義:在運作期才能确定的語義,比如将0作為除數是一個運作期語義錯誤。

4.中間語言生成  -- 一個優化過程

  現代的編譯器有着很多層次的優化,這裡介紹的是一個源碼級優化器(Source Code Optimizer),會在源碼級别進行優化。比如例子中的(2 + 6),因為在編譯階段可以确定為8,是以這個表達式被優化掉了。

編譯到底做了什麼(***.c -> ***.o的過程)

 因為直接在文法樹上做優化是比較困難的,是以源代碼優化器往往将整個文法樹轉換成中間代碼(Intermediate Code),就是文法樹的順序表示(已經非常接近目标代碼了)。

 中間代碼有很多類型,在不同的編譯器有着不同的表現形式,常見的有:三位址碼(Three-address Code)、P代碼(p-Code)。

中間代碼使得編譯器可以分成前端和後端。

前端:負責産生機器無關的中間代碼

後端:将中間代碼轉換成目标代碼

5.目标代碼生成與優化(這裡開始就是後端了,前面都是前端)

 編譯器後端主要包括:代碼生成器(Code Generator)和目标代碼優化器(Target Code Optimizer)。

 代碼生成器:将中間代碼轉換成目标機器代碼。這個過程非常依賴于機器,因為不同的機器有不同的字長,寄存器,整數資料類型和浮點數資料類型等。

對于我們的例子,可能會生成下面的代碼序列(用x86的彙編來表示),如圖

編譯到底做了什麼(***.c -> ***.o的過程)

目标代碼優化器:對上述的目标代碼進行優化。比如:選擇合适的尋址方式,使用位移來代替乘法運算,删除多餘的指令等。

對于我們的例子,有可能會優化成這個樣子。

如圖。

編譯到底做了什麼(***.c -> ***.o的過程)

------  我是分割線   ------

好了,忙活了這麼久,源代碼終于變成了目标代碼。

這時候問題來了,index和array的位址還沒有确定。若用把目标代碼用彙編器編譯成真正能在機器上執行的指令,這兩個位址從何而來呢。

若index和array定義在跟上面的源代碼同一個編譯單元裡,那麼編譯器可以為它們配置設定空間,确定它們的位址。

若定義在其他子產品呢?說來就話長了。。。。。。

附在那本書的一些話:(助于了解)

(1).現代的編譯器可以将一個源代碼檔案編譯成一個未連結的目标檔案,然後由連結器最終将這些目标檔案連結起來形成可執行檔案。

(2).彙編器是将彙編代碼轉變成機器可以執行的指令,每一個彙編語句幾乎都對應一條機器指令。

(3).是以彙編器的彙編過程相對于編譯器來講比較簡單,它沒有複雜的文法,也沒有語義,也不需要做指令優化,隻是根據彙編指令和機器指令的對照表一一翻譯就可以了。

(4).經過預編譯、編譯和彙編直接輸出目标檔案(Object File)。

參考文獻《程式員的自我修養--連結、裝載與庫》 P41-P48 (其實就是摘抄整理了一下,哈哈)

繼續閱讀