1.預處理(Pre-Processing)
2.編譯(Compiling)
3.匯編(Assembling)
4.鏈接(Linking)
1.預處理(Pre-Processing)
讀取c源程序,對其中的偽指令(以#開頭的指令)和 預定義符號進行處理
偽指令主要包括以下四個方面
(1)宏定義指令:
如#define,#undef。
(2)條件編譯指令:
如#ifdef,#ifndef,#else,#elif,#endif,等等。
這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序對哪些代碼進行處理。預編譯程序將根據有關的文件,將那些不必要的代碼過濾掉
(3)頭文件包含指令:
如#include "FileName"或者#include <stdio.h>等。
使用< >引用的頭文件是告訴編譯器要到系統指定的目錄下去尋找
而使用 " " 引用的頭文件是告訴編譯器先在默認目錄下查找, 如果默認目錄下找不到則再到系統目錄下查找
一般情況下, 你自己寫的頭文件用 " ",編譯器自帶的頭文件用< >
這樣方便區別頭文件的類型
如果全部用 " "也是可以的, 只不過不符合規范結構而已。
(4)預定義符號,預編譯程序可以識別一些預定義符號。
C語言編譯器的預定義符號:
__LINE__ 當前(源代碼文件)行號 [整數]
__FILE__ 當前正在編譯的文件的文件名 [字符串]
__DATE__ 當前日期,以“月月 日日 年年年年”的形式給出 [字符串]
__TIME__ 當前時間,以“HH:mm:ss”的格式給出 [字符串]
__STDC__ 如果編譯器符合ANSI C標準,該宏為1,否則為0
__STDC_HOSTED__ 如果實現了所有C標準庫,該宏為1,否則為0
__STDC_VERSION__ 被定義為199901L(不同編譯器可能不一樣,比如gcc里就沒有這個預定義符號)
注:這些預定義符號的首尾為兩個下劃線,如果是兩個單詞,中間以一個下劃線連接。
如果在源代碼中使用了這些符號,它們會在預處理時被轉換(使用gcc編譯器的 -E 選項可以看到替換后的值)
C 標準里還在每個函數內預定義了一個標志符: __func__
它被定義為 static const char __func__[]="function-name";
即不能在程序內對__func__賦值,也不能改變它所指向的字符串(函數名),否則報編譯錯誤
注:__func__是個標志符,它在預處理階段不被替換,所以使用gcc -E 是看不到任何效果的。
預編譯程序對于在源程序中出現的預定義符號將用合適的值進行替換。
預編譯程序所完成的基本上是對源程序的“替代”工作。
經過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個文件的含義同沒有經過預處理的源文件是相同的,但內容有所不同。下一步,此輸出文件將作為編譯程序的輸出而被翻譯成為機器指令。
2.編譯(Compiling)
預編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼。
3.匯編(Assembling)
匯編過程實際上指把匯編語言代碼翻譯成目標機器指令的過程。對于被翻譯系統處理的每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。
匯編階段就是把編譯階段生成的".s"文件轉成目標文件。
4.鏈接(Linking)
由匯編程序生成的目標文件并不能立即就被執行,其中可能還有許多沒有解決的問題。例如,某個源文件中的函數可能引用了另一個源文件中定義的某個符號(如變量或者函數調用等);
在程序中可能調用了某個庫文件中的函數,等等。所有的這些問題,都需要經鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成為一個能夠誒操作系統裝入執行的統一整體。
根據開發人員指定的同庫函數的鏈接方式的不同,鏈接處理可分為兩種:
(1)靜態鏈接
在這種鏈接方式下,函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中。
(2)動態鏈接
在此種方式下,函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。
經過上述4個過程,C源程序就最終被轉換成可執行文件了。
gcc一些編譯選項:
-std=<標準> 指定輸入源文件遵循的標準
--sysroot=<目錄> 將 <目錄> 作為頭文件和庫文件的根目錄
-B <目錄> 將 <目錄> 添加到編譯器的搜索路徑中
-E 僅作預處理,不進行編譯、匯編和鏈接
-S 編譯到匯編語言,不進行匯編和鏈接
-c 編譯、匯編到目標代碼,不進行鏈接
-o <文件> 輸出到 <文件>