C+程序的編譯包括三個(gè)步驟:
預(yù)處理:預(yù)處理程序采用C+源代碼文件,并處理#include
S,#define
S和其他預(yù)處理指令。這個(gè)步驟的輸出是一個(gè)“純”C+文件,沒有預(yù)處理指令。
編譯:編譯器獲取預(yù)處理器的輸出并從中生成一個(gè)對(duì)象文件.
鏈接:鏈接器獲取編譯器生成的對(duì)象文件,并生成庫或可執(zhí)行文件。
預(yù)處理
預(yù)處理程序處理預(yù)處理指令,就像#include
和#define
。它不知道C+的語法,這就是為什么必須謹(jǐn)慎使用它的原因。
它一次工作在一個(gè)C+源文件上,方法是#include
具有相應(yīng)文件內(nèi)容的指令(通常只是聲明),替換宏(#define
),并根據(jù)以下內(nèi)容選擇文本的不同部分:#if
, #ifdef
和#ifndef
指令。
預(yù)處理器工作在預(yù)處理令牌流上。宏替換定義為用其他令牌替換令牌(操作符)。##
當(dāng)有意義時(shí),啟用合并兩個(gè)令牌)。
在所有這些之后,預(yù)處理器產(chǎn)生一個(gè)單一的輸出,這是由上面描述的轉(zhuǎn)換產(chǎn)生的令牌流。它還添加了一些特殊的標(biāo)記,告訴編譯器每一行來自哪里,這樣它就可以使用這些標(biāo)記來生成合理的錯(cuò)誤消息。
在此階段,可以通過巧妙地使用#if
和#error
指令。
編撰
編譯步驟對(duì)預(yù)處理程序的每個(gè)輸出執(zhí)行。編譯器解析純C+源代碼(現(xiàn)在沒有任何預(yù)處理器指令)并將其轉(zhuǎn)換為程序集代碼。然后調(diào)用底層后端(工具鏈中的匯編程序),將該代碼組裝成生成某種格式的實(shí)際二進(jìn)制文件的機(jī)器代碼(ELF,COFF,a.out,.)。此對(duì)象文件包含輸入中定義的符號(hào)的編譯代碼(二進(jìn)制形式)。對(duì)象文件中的符號(hào)按名稱引用。
對(duì)象文件可以引用未定義的符號(hào)。如果使用聲明,而不提供聲明的定義,則會(huì)出現(xiàn)這種情況。編譯器不介意這一點(diǎn),只要源代碼格式良好,編譯器就會(huì)很高興地生成對(duì)象文件。
編譯器通常允許您在此時(shí)停止編譯。這非常有用,因?yàn)槭褂盟?,您可以分別編譯每個(gè)源代碼文件。它提供的優(yōu)點(diǎn)是您不需要重新編譯一切如果您只更改一個(gè)文件。
生成的對(duì)象文件可以放在稱為靜態(tài)庫的特殊檔案中,以便以后更容易重用。
在這個(gè)階段,報(bào)告了“常規(guī)”編譯器錯(cuò)誤,比如語法錯(cuò)誤或失敗的過載解析錯(cuò)誤。
鏈接
鏈接器是編譯器生成的對(duì)象文件的最終編譯輸出。這個(gè)輸出可以是一個(gè)共享的(或者是動(dòng)態(tài)的)庫(雖然名稱相似,但它們與前面提到的靜態(tài)庫沒有多少共同點(diǎn)),也可以是一個(gè)可執(zhí)行文件。
它通過用正確的地址替換對(duì)未定義符號(hào)的引用來鏈接所有對(duì)象文件。這些符號(hào)中的每一個(gè)都可以在其他對(duì)象文件或庫中定義。如果它們是在標(biāo)準(zhǔn)庫以外的庫中定義的,則需要將它們告知鏈接器。
在這個(gè)階段,最常見的錯(cuò)誤是缺少定義或重復(fù)定義。前者意味著定義不存在(即它們不被寫入),或者它們所在的對(duì)象文件或庫沒有提供給鏈接器。后者是顯而易見的:在兩個(gè)不同的對(duì)象文件或庫中定義了相同的符號(hào)。