預處理是編譯前的文本處理階段,由預處理器(cpp)自動完成。以 # 開頭的指令不屬於 C 語法,不以分號結束。核心功能:宏定義、文件包含、條件編譯。合理使用可大幅提升代碼可讀性、可移植性與模塊化程度。
宏定義建立標識符 → 替換文本的映射。預處理階段對源碼中所有出現的宏名進行宏展開(文本替換),隨後才進入編譯器詞法/語法分析。
#define IDENTIFIER replacement-text
IDENTIFIER:宏名,慣用全大寫。replacement-text:任意字符序列(常數、表達式、代碼片段、甚至多行)。#define PI 3.1415926
#define MAX_SIZE 1024
#define DEBUG 1
#define SQUARE(x) ((x)*(x)) // 帶參宏見下文
// 無參表達式宏
#define AREA_CIRCLE (PI * r * r) // 危險:依賴外部變量 r
// 較安全:將表達式括起來
#define EXPR (y*y + 3*y)
#define M (y*y + 3*y)
int s = 3*M + 4*M + 5*M;
// 展開後:
int s = 3*(y*y + 3*y) + 4*(y*y + 3*y) + 5*(y*y + 3*y);
// 若定義無括號:#define M y*y + 3*y
// 展開:3*y*y + 3*y + 4*y*y + 3*y + 5*y*y + 3*y -> 錯誤結果
#define EXPR ( ... ) 防止優先級干擾。#define SQUARE(x) ((x)*(x)) 防止傳入表達式時運算順序錯亂。#define SQ(y) (y)*(y)
int sq = 160 / SQ(a+1); // a=3
// 展開:160 / (a+1)*(a+1) = 160/4*4 = 160 (預期 10)
// 修正:#define SQ(y) ((y)*(y))
// 再修正:#define SQ(y) (((y)*(y))) // 整體再括一次
| 維度 | 宏 | 函數 |
|---|---|---|
| 處理階段 | 預處理(文本替換) | 編譯/鏈接(代碼生成) |
| 類型檢查 | 無 | 有 |
| 參數求值 | 不求值,原樣替換 | 求值後傳值(值傳遞) |
| 副作用 | 參數多次展開導致多次求值 | 參數求值一次 |
| 調試 | 不可單步調試,行號混亂 | 完整調試支持 |
| 代碼膨脹 | 每次調用展開,增加體積 | 單一實體,調用開銷固定 |
| 遞歸 | 不支持 | 支持 |
#define SQ(y) ((y)*(y))
int i = 1;
while (i <= 5) {
printf("%d\n", SQ(i++)); // i++ 展開兩次!
}
// 展開:((i++)*(i++))
// 輸出:2, 12, 30 ... 而非 1,4,9,16,25
#define SSSV(s1,s2,s3,v) s1=l*w; s2=l*h; s3=w*h; v=w*l*h;
// 調用:SSSV(sa,sb,sc,vv);
// 展開為 4 賦值語句
缺陷:若放在 if 後無大括號,只執行第一句。標準解法見下章帶參宏。
const/enum/inline 函數替代宏時,優先不用宏。