《讀書筆記》程序員的自我修養之編譯和連接前端
對於經典的Hello world,程序是如何運行的呢?linux
#include <stdio.h>程序員
int main()算法
{後端
printf("Hello World\n");函數
return 0;工具
}優化
對於GCC編譯器,程序運行分爲如下四個過程:spa
預編譯(Prepressing)--à編譯(Compilation)--à彙編(Assembly)--à連接(Linking)翻譯
預編譯是將.C文件預編譯成.i文件
$gcc –E hello.c –o hello.i
或
$cpp hello.c > hello.i
注:‘-E’選項表示只進行預編譯;cpp是預編譯器
預編譯過程主要處理源代碼文件中的以「#」開始的預編譯指令(「#include」,「#define」等)。主要處理規則以下:
編譯是整個過程的核心,也是最複雜部分之一。包括詞法分析、語法分析、語義分析、源代碼優化,生成彙編代碼。
編譯過程命令以下:
$gcc –S hello.i –o hello.s
目前的GCC版本將預編譯和編譯過程合二爲一,使用一個叫作ccl的程序來完成這兩個過程。
可直接調用ccl來完成預編譯和編譯過程,以下:
$ /user/lib/gcc/i486–linux–gnu/4.1/ccl hello.c
或者
$gcc –S hello.c –o hello.s
彙編器(as)是將彙編代碼轉變爲機器可執行的指令(彙編指令和機器指令的對照表一一翻譯)
彙編過程以下:
$as hello.s –o hello.o
或者
$gcc –c hello.s –o hello.o
或者
$gcc –c hello.c –o hello.o
連接的過程是連接器(ld)將目標文件(.o文件)變爲可執行文件(.exe)的過程。
連接過程是一個複雜的過程,至關複雜!!!
$ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o
看了上面的命令,爲何要將一大堆的文件連接起來才能夠獲得可執行文件??
且看後面的靜態連接與動態連接篇幅。
上面分析了一段程序的執行過程,下面具體看看編譯器到底作了什麼工做。
編譯過程通常能夠分爲6步:掃描(詞法分析)、語法分析、語義分析、源代碼優化、代碼生成和目標代碼優化。
一、掃描器(Scanner)的任務就是進行簡單的詞法分析,運用一種相似於有限狀態機(Finite State Machine)的算法將源代碼的字符序列分割成一系列的記號(Token)。詞法分析產生的記號通常有如下幾類:關鍵字、標識符、字面量(包含數字、字符串等)和特殊符號(如加號、等號)。掃描的過程由lex程序完成。
二、語法分析器(Grammar Parser)將對由掃描器產生的記號進行語法分析,從而產生語法樹(Syntax Tree)。簡單來說,由語法分析器生成的語法樹就是以表達式(Expression)爲節點的樹。語法分析的過程由yacc工具完成。
三、語義分析由語義分析器(Semantic Analyzer)來完成。
----編譯器所能分析的語義是靜態語義,與之對應的動態語義只有在運行期才能肯定。
----靜態語義一般包括聲明和類型的匹配,類型的轉換。
----通過語義分析後,語法分析生成的語法樹的表達式都被標識了類型,若是有些類型須要隱式轉換,語義分析程序會在語法樹中插入相應的轉換節點。
四、源代碼優化器對源代碼進行優化,但直接在語法樹上做優化比較困難,每每是將整個語法樹轉換成中間代碼進行優化。
中間代碼使得編譯器分爲前端和後端:前端負責產生機器無關的中間代碼;後端負責將中間代碼轉換成目標機器代碼。
五、目標代碼優化器將上述生成的目標機器代碼進行優化,好比選擇合適的尋址方式、使用位移代替乘法運算、刪除多餘的指令等。
編譯器忙活了半天,可生成的目標代碼中,咱們還不知道函數訪問所要的目標函數的地址,變量訪問所要的目標變量的地址,這可咋辦呢??
其實目標函數訪問也好,變量訪問也好,這均可以歸結爲一種方式,就是所謂的模塊間符號的引用。
人們把每一個源代碼模塊獨立地編譯,而後按照須要將它們組裝起來,這個組裝模塊的過程就是「連接」。
連接的主要內容就是把各個模塊之間相互引用的部分處理好,使得各個模塊之間可以正確銜接。
連接過程主要包括地址和空間分配、符號決議和重定位。