記錄C++語言相關的基礎知識
1 C++源碼到可執行文件的四個階段
預處理(.i)、編譯(.s)、匯編(.obj)、鏈接。
1.1 預處理
預處理階段,主要完成宏替換、文件展開、注釋刪除、條件編譯展開、添加行號和文件名標識,輸出.i/.ii預處理文件。
- 宏替換,對于使用#define定義的值直接進行文本替換
- 文件展開,對于#include h文件,直接對包含的文件進行展開
- 刪除注釋信息。保留#pragma 編譯器指令。
- 根據條件編譯參數進行條件編譯的展開
- 添加行號和文件名標識
例如g++ -E compiler_test.cc -o compiler.i -DUSER_DEBUG=ON
, 可以指定只生成預處理后的文件。圖中省略了#include<stdio.h>即對應的預處理后文件內容。
1.2 編譯
對預處理文件進行詞法、語法、語義分析和優化,產生對應的匯編代碼,是構建過程中最復雜的部分。編譯參數中-O選項就是在這一步生效的。
- 詞法分析, 將源代碼通過掃描器分析出源代碼中的每一部分分別對應的類型是什么(tokens),例如數字、括號、運算符、賦值等。
- 語法分析,根據tokens進行語法分析,生成語法樹(表達式組成的樹,運算優先級會在此時確定, 源代碼級別的優化也會在此時進行)。
- 語義分析,編譯階段進行的是靜態語義分析,也就是檢查語法樹的類型是否匹配、進行類型轉換、表達式是否有意義,語法樹在這個階段會被標記上語義。
- 中間語言生成, 在語法樹上對代碼直接進行優化比較困難, 源代碼優化器往往是把整個語法樹轉化成中間代碼(與運行環境無關)。中間代碼的出現將整個編譯過程分成了兩部分,前端部分將源代碼轉化成機器無關的中間代碼,后端部分將后端代碼轉化為目標機器代碼。
- 目標代碼生成與優化,通過代碼生成器將中間語言轉化為匯編代碼,代碼優化器針對匯編代碼進行優化。
g++ -S compiler.i -o compiler.s
1.3 匯編
將匯編代碼轉化為機器可以執行的指令,生成目標文件(.o, .obj)。這個過程就是匯編器對匯編語言的翻譯過程。
g++ -C compiler.s add.s -o compiler.o
1.4 鏈接
C++的每個源代碼是獨立編譯的,會存在不同的目標文件相互引用的情況,這個時候目標文件中的符號(變量、函數等)還沒有真實的地址(這個階段的地址取的是0),經過鏈接將不同的目標文件進行組合。主要是進行地址和空間分配、地址綁定、重定位, 將目標文件轉化為可執行文件。
2 模板為什么通常不是分開聲明和實現
關鍵一點是模板的實例化是要在編譯單元內看到了模板實例化定義,而編譯單元是每個cpp文件,如此一來在模板的cpp文件中,由于沒有使用到模板,所以在編譯模板時根本不會實例化。在其他cc文件中引用時,在鏈接階段就會報錯。而在模板cc文件中實現模板定義
template <typename T> T Add(const T &left, const T &right) {return left + right;
}
template int Add(const int&, const int&);
通常是不合適的,首先是不知道用戶到底會實例化什么類型,其次代碼很繁瑣,所以一般都是在h文件中聲明和實現。
3 常用的編譯工具
gcc/g++、 automake、cmake、bazel