1. 程式人生 > >一個cpp檔案的編譯過程詳解

一個cpp檔案的編譯過程詳解

一個CPP檔案的編譯過程

籠統的說一個CPP檔案的編譯過程就是以下幾步

Created with Raphaël 2.1.2預處理(做優化,生成.i檔案)編譯器(生成.s檔案)彙編器(生成.o檔案) 連結器(連線庫檔案和其他目的碼) 生成可執行檔案

c++為了相容c程式,沒有選擇像java或者python之類的import當前原始檔所用到的庫,而是以include標頭檔案的方式來將庫的藉口宣告以文字替換的方式載入,然後重新解析,一個簡單的helloworld程式,預處理的時候會讀入將近20個頭檔案,預處理之後供編譯器parse的原始碼有上萬行。

g++ -E testHello.cpp -o testHello.i

這裡寫圖片描述

c++預處理階段主要完成的工作:處理#開始的預編譯指令:
(1)巨集定義(#define):對程式中所有出現的巨集名,都用巨集定義中的字串去代換
(2)檔案包含(#include):檔案包含命令把指定標頭檔案插入該命令列位置取代該命令列,從而把指定的檔案和當前的源程式檔案連成一個原始檔。
(3)條件編譯(#ifdef):一般情況下,源程式中所有的行都參加編譯。但有時希望對其中一部分內容只在滿足一定條件才進行編譯,也就是對一部分內容指定編譯的條件,這就是“條件編譯”。有時,希望當滿足某條件時對一組語句進行編譯,而當條件不滿足時則編譯另一組語句。條件編譯功能可按不同的條件去編譯不同的程式部分,從而產生不同的目的碼檔案。這對於程式的移植和除錯是很有用的。

彙編階段直接生成了彙編程式碼,具體過程不用贅述

連結器階段則非常複雜,這裡只簡要敘述下主要的任務
(1)函式過載:c++編譯器為實現函式過載普遍使用的是名字改編的方式為每個函式生成一個獨一無二的名字,連結的時候就能找到正確的過載版本
(2)inline函式:如果函式體不太大,對此函式的所有呼叫都以函式本體去替代,注意inline只是對編譯器的一個建議申請,不是強制命令
(3)模板處理:函式定義(包括具現化後的函式模板,類模板的成員函式),變數定義(包括函式模板的靜態資料變數,類模板的靜態資料變數,類模板的全域性物件等)
(4)虛擬函式:每一個多型class都有一份虛擬函式表,定義或繼承了虛擬函式的物件會有一個隱含成員:指向虛表的指標vptr,在構造或析構物件的時候,編譯器生成的程式碼會修改這個指標。按道理說,一個多型class的虛表應該恰好被一個目標檔案定義,這樣連結就不會有錯,但c++編譯器有時無法判斷是否應該在當前編譯單元生成虛表定義,為保險起見,只能每個編譯單元都生成虛表,然後交給連結器來消除重複資料。

c++使用的也是c語言的單遍編譯的方式,意思就是從頭到尾掃描一遍原始碼,一邊解析原始碼,一邊即刻生成目的碼,這麼做的原因是:編譯器沒辦法在記憶體裡完整的表示單個原始檔的抽象語法樹,更不可能把整個程式(由多個原始檔組成)放進記憶體裡,以完成交叉引用(不同原始檔的函式相互呼叫,使用外部變數等)