C++編譯特點
- 與C兼容
不僅是語法兼容,更重要的是兼容C語言的編譯模型與運行模型,也就是能直接使用 C語言的頭文件。
- C++語言的三大約束
與C兼容,零開銷,值語義。
單遍編譯,C++繼承了C的單遍編譯,但是影響了名字查找和函數重載決議。從頭到尾掃描一遍源碼,一邊解析源碼,一邊生成目標代碼,也就是編譯時,只能看到目前已經解析過的源碼,看不到後面的源碼,也是C語言中需要函數聲明的原因。結構體必須先定義。
重載,爲了實現函數重載,c++編譯器採用名字改編的辦法,爲每個重載函數生成獨一無二的名字,這樣鏈接的時候就能找到正確的函數。返回類型不參與函數重載。函數重載決議,當C++編譯器讀到一個函數調用時,它必須從已經看到的同名函數中選出最佳的函數,哪怕後面出現了更合適的匹配。
C++ 只能通過解析源碼來了解名字的含義。意味着要準確理解一行代碼的意義,需要通讀之前的所有代碼。AA BB(CC);這句話既可以聲明函數,也可以定義變量,代碼的行爲跟AA BB的完整定義有關。
編譯過程
編譯的步驟
preprocessor /complier /assembler /linker
1. 每個源文件作爲一個編譯單元,可能會包含上百甚至上千個頭文件。每一個編譯單元,這些頭文件都會被從硬盤讀進來一遍,然後被解析一遍。
2. 每個編譯單元都會產生一個obj文件,然後所以這些obj文件會被link到一起。
一個C++可執行文件是debug build 還是 release build?通常的做法是判斷class template 的短函數成員有沒有被inline展開。
g++ -Wall *.cc 沒有優化的bulid,不會inline展開。debugbuild.
g++ -Wall -o2 *.cc inline展開。releasebuild. 編譯器自動生成的析構函數也是inline的。
編譯的結果是靜態庫,動態庫,或程序。
1、 一致的內存管理,Linux共享庫與應用程序共享一個heap,因此動態庫分配的內存可以交給應用程序去釋放。
2、 一致的初始化,動態庫裏的靜態對象的初始化和程序其他地方的靜態對象一樣。
3、 在動態庫中可以放心使用class, STL。
由於C++ 的頭文件與源文件分離,並且目標文件裏沒有足夠的元數據供編譯器使用,因此必須同時提供庫文件和頭文件。也就是說要想使用一個已經編譯好的C/C++ 庫(無論是靜態庫還是動態庫),我們需要兩樣東西,一是頭文件(.h),二是庫文件(.a 或.so),這就存在了兩樣東西不匹配的可能。
如果替換了某個程序用到的動態庫,先前運行正常的這個程序使用的將不再是當初build 和測試時的代碼。結果是程序的行爲變得不可預期。
一旦動態庫或靜態庫可能頻繁更新,爲了避免頭文件和庫文件不一致,建議直接使用庫源代碼。這樣徹底避免頭文件與庫文件之間的時間差,也不用爲庫的版本搭配操心。這麼做的缺點是編譯時間很長。
編譯加速
1. 在頭文件中使用前置聲明,而不是直接包含頭文件。
很多時候前置聲明某個namespace中的類會比較痛苦,而直接include會方便很多,千萬要抵制住這種誘惑;類的成員,函數參數等也儘量用引用,指針,爲前置聲明創造條件。
2. 高度模塊化
不要把兩個不相關的類,或者沒什麼聯繫的宏定義放到一個頭文件裏。內容要儘量單一,從而不會使包含他們的文件包含了不需要的內容。代碼中最"hot"的那些頭文件找出來,然後分成多個獨立的小文件,效果相當可觀。
3. 特別注意inline和template
這是C++中兩種比較"先進"的機制,但是它們卻又強制我們在頭文件中包含實現。
4. 不要有太多的AdditionalInclude Directories
編譯器定位你include的頭文件,是根據你提供的include directories進行搜索的。
5. 並行編譯和更好的磁盤
CPU有8核的,每次一build,就是8個文件並行着編。編譯速度慢很大一部分原因是磁盤操作。make的-j參數可以使make進行並行編譯。