宏定義不僅是簡單的文本替換,C 預處理器提供了 參數連接符 ## 與 字符串化運算符 # 兩大進階機制,能實現變量名自動生成、調試輸出自動化等強大功能。本文通過實例深度解析這兩大技巧的原理、陷阱與最佳實踐。
#define FUN() \
int i = 0; \
i++; \
printf("%d\n", i)
void test() {
FUN(); // 第一次展開
FUN(); // 第二次展開 → 錯誤:重定義變量 i
}
宏展開後:
void test() {
int i = 0; i++; printf("%d\n", i);
int i = 0; i++; printf("%d\n", i); // 編譯錯誤:i 重定義
}
## 連接符生成唯一變量名__LINE__ 宏實現唯一性#define FUN() \
int i##__LINE__ = 0; \
i##__LINE__++; \
printf("%d\n", i##__LINE__)
void test() {
FUN(); // 展開為 i12
FUN(); // 展開為 i13 (假設行號 12、13)
}
展開結果:
void test() {
int i12 = 0; i12++; printf("%d\n", i12);
int i13 = 0; i13++; printf("%d\n", i13); // 無衝突
}
## 連接符規則詳解#:參數變字符串字面量#define STR(x) #x
STR(hello) // 展開為 "hello"
STR(123) // 展開為 "123"
STR(a + b) // 展開為 "a + b" (保留空格、運算符)
#define DEBUG_PRINT(expr) \
printf(#expr " = %d\n", expr)
int x = 10, y = 20;
DEBUG_PRINT(x + y); // 輸出:x + y = 30
展開過程:printf("x + y" " = %d\n", x + y); → 相鄰字符串字面量自動合併。
#define ASSERT(cond) \
do { \
if (!(cond)) { \
printf("ASSERT failed: %s, file %s, line %d\n", \
#cond, __FILE__, __LINE__); \
exit(1); \
} \
} while (0)
// 使用
ASSERT(ptr != NULL);
ASSERT(index >= 0 && index < MAX);
| 陷阱 | 現象 | 解決 |
|---|---|---|
| 宏參數副作用 | SQUARE(x++) 導致 x 增加兩次 | 改用 inline 函數或 GCC statement expression |
## 連接非標識符 | PASTE(1, 2) → 12(合法);PASTE(+, -) → +-(非法) | 確保連接結果為合法 Token |
| 多行宏缺少反斜槓 | 宏體換行未轉義,僅第一行生效 | 每行結尾加 \,最後一行不加 |
| 宏名與庫函數衝突 | 定義 #define malloc(n) my_malloc(n) 破壞標準庫 | 宏名加項目前綴,如 MY_MALLOC |
const/enum/inline 解決的,不用宏:類型安全、可調試、作用域受控MAX_SIZE)#define SQR(x) ((x)*(x)))do {...} while(0) 包裹## 生成唯一標識符、# 自動字符串化gcc -E -P file.c 查看預處理結果掌握 ## 與 #,能將宏從「簡單替換」提升為「元編程工具」,但請記住:宏無類型、無作用域、難調試,現代 C/C++ 開發中優先考慮模板、constexpr、inline 等語言級特性,宏僅作為最後手段。