順序,大型項目的文件之間是有一定的相互依賴關系的,而編譯順序存放在makefile這個文件當中,我們寫makefile其實就是干了這么一件事。?
2.編譯過程之預處理
這個過程主要做以下幾個方面的工作,最主要的工作其實就是做一些簡單的翻譯。
比如:
(1)宏定義指令,如 #define a b? 需要替換成最開始的樣子。
(2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。?根據(jù)環(huán)境,哪些需要編譯,哪些不需要編譯。
(3) 頭文件包含指令,如#include 'FileName'或者#include等。?就是將頭文件換成源碼級別的聲明。
(4)特殊符號,預編譯程序可以識別一些特殊的符號。比如類似于行號,文件名等。
預處理生成hello.i文件
頭文件的內容看上去很多,但是關鍵的其實也就兩三句,這也是為什么后面我們看到匯編語言只有一點點。
3.編譯過程之編譯優(yōu)化
依然是一個翻譯過程,將c語言級別的源碼翻譯成匯編語言。
優(yōu)化階段,經(jīng)過預編譯得到的輸出文件中,只有常量;如數(shù)字、字符串、變量的定義,以及C語言的關鍵字,如main,if,else,for,while,{,},+,-,*,\等等。?
這個時候已經(jīng)有了段的概念,截一下過程當中的圖:
編譯優(yōu)化生成匯編的過程
而這個優(yōu)化過程主要是語法還有句法分析,在確認所有的指令都符合語法規(guī)則之后,將其翻譯成等價的中間代碼表示或匯編代碼。
優(yōu)化比如循環(huán)控制,無用值得剔除,當然了匯編當中寄存器配置的優(yōu)化,從而減少訪問內存的次數(shù)。針對不同的指令集找最佳的指令來完成這個工作。
4.編譯過程之匯編
就是將?之前的匯編語言轉成二進制碼,轉碼以后的文件我們稱為目標文件,也就是一般的.o后綴的文件。
目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。目標文件由段組成。通常一個目標文件中至少有兩個段:
代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執(zhí)行的,但一般卻不可寫。
數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫,可執(zhí)行的。
UNIX環(huán)境下主要有三種類型的目標文件:
(1)可重定位文件
其中包含有適合于其它目標文件鏈接來創(chuàng)建一個可執(zhí)行的或者共享的目標文件的代碼和數(shù)據(jù)。(還有一些位置并沒有補充上去,地址什么的也需要重新處理)
(2)共享的目標文件(比如dll,是運行時內存共享的位置)
這種文件存放了適合于在兩種上下文里鏈接的代碼和數(shù)據(jù)。第一種是鏈接程序可把它與其它可重定位文件及共享的目標文件一起處理來創(chuàng)建另一個目標文件;第二種是動態(tài)鏈接程序將它與另一個可執(zhí)行文件及其它的共享目標文件結合到一起,創(chuàng)建一個進程映象。
(3)可執(zhí)行文件
它包含了一個可以被操作系統(tǒng)創(chuàng)建一個進程來執(zhí)行之的文件。匯編程序生成的實際上是第一種類型的目標文件。對于后兩種還需要其他的一些處理方能得到,這個就是鏈接程序的工作了。
5.鏈接過程生成可執(zhí)行文件
(這個時候主要是鏈接靜態(tài)的庫,直接拷貝到一起,動態(tài)鏈接登記一下信息)
主要理解使用符號,定位另一個文件當中的定義,形成跳轉什么的?
由匯編程序生成的目標文件并不能立即就被執(zhí)行,其中可能還有許多沒有解決的問題。例如,某個源文件中的函數(shù)可能引用了另一個源文件中定義的某個符號(如變量或者函數(shù)調用等);在程序中可能調用了某個庫文件中的函數(shù),等等。所有的這些問題,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成為一個能夠誒操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
根據(jù)開發(fā)人員指定的同庫函數(shù)的鏈接方式的不同,鏈接處理可分為兩種:
(1)靜態(tài)鏈接
在這種鏈接方式下,函數(shù)的代碼將從其所在地靜態(tài)鏈接庫中被拷貝到最終的可執(zhí)行程序中。這樣該程序在被執(zhí)行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態(tài)鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數(shù)的代碼。
(2) 動態(tài)鏈接
在此種方式下,函數(shù)的代碼被放到稱作是動態(tài)鏈接庫或共享對象的某個目標文件中。鏈接程序此時所作的只是在最終的可執(zhí)行程序中記錄下共享對象的名字以及其它少量的登記信息。在此可執(zhí)行文件被執(zhí)行時,動態(tài)鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態(tài)鏈接程序將根據(jù)可執(zhí)行程序中記錄的信息找到相應的函數(shù)代碼。
對于可執(zhí)行文件中的函數(shù)調用,可分別采用動態(tài)鏈接或靜態(tài)鏈接的方法。使用動態(tài)鏈接能夠使最終的可執(zhí)行文件比較短小,并且當共享對象被多個進程使用時能節(jié)約一些內存,因為在內存中只需要保存一份此共享對象的代碼。但并不是使用動態(tài)鏈接就一定比使用靜態(tài)鏈接要優(yōu)越。在某些情況下動態(tài)鏈接可能帶來一些性能上損害。
這個之后就形成了我們所說的PE文件結構,之后?會有文章詳細說明這個文件結構。
6.安裝過程
程序到了第五步,實際上已經(jīng)可以運行。以后的工作就是程序放到指定位置,創(chuàng)建目錄,設置權限,與操作系統(tǒng)交互,進行文件的關聯(lián),主要是環(huán)境的配置。
于是之后程序開始運行,唯一需要注意的是運行的時候程序需要先尋找??動態(tài)鏈接庫,這個是在內存當中,也就是多個程序所共享,虛擬地址空間的情形下,他們的地址實際都一樣,有跳轉表然后執(zhí)行函數(shù)過程。
簡單附上進行gcc編譯過程的語句:
以下整個的過程實際上就是一個make指令的完整的工作過程。?
預編譯
將.c 文件轉化成 .i文件
使用的gcc命令是:gcc –E(完整應該時gcc –E filename.cpp -o filename.i 后面的類似)對應于預處理命令cpp
編譯
將.c/.h文件轉換成.s文件
使用的gcc命令是:gcc –S
對應于編譯命令 cc –S
匯編
將.s 文件轉化成 .o文件
使用的gcc 命令是:gcc –c
對應于匯編命令是 as
鏈接??
將.o文件轉化成可執(zhí)行程序
使用的gcc 命令是: gcc
對應于鏈接命令是 ld
另外需要注意實際的鏈接命令可能并不work,直接使用GCC會比較好。?