#define 是 C/C++ 預處理器的核心指令,通過文本替換實現常量定義、宏函數、條件編譯等功能。雖然現代 C++ 推薦使用 const、constexpr、inline 函數與模板替代宏,但在嵌入式開發、跨平台適配、舊代碼維護等場景中,宏仍不可或缺。本文系統梳理宏定義的各類用法、常見陷阱與最佳實踐。
#define MAX_TIME 1000
預處理階段將所有 MAX_TIME 替換為 1000。與 const int MAX_TIME = 1000; 不同:
#undef,不受作用域規則約束。#define MAX(x, y) ((x) > (y) ? (x) : (y))
看似函數,實則文本展開。優點:無函數調用開銷,類型無關(類模板雛形)。缺點:
#define ADD(a, b) a + b
int c = 10 * ADD(2, 3); // 展開為 10 * 2 + 3 = 23,非預期的 50
修正:參數與整體表達式雙重加括號:#define ADD(a, b) ((a) + (b))。
int x = 5;
int y = MAX(++x, 10); // x 增加兩次,結果不可預期
#define PTR int*
PTR p1, p2; // 展開為 int* p1, p2; p2 是 int 而非 int*
對策:使用 typedef int* PTR; 或 using PTR = int*;。
| 操作符 | 功能 | 示例 | 結果 |
|---|---|---|---|
# | 參數字符串化 | #define STR(x) #x | STR(abc) → "abc" |
## | 標記粘連 | #define CONCAT(a, b) a##b | CONCAT(var, 1) → var1 |
#@ | 字符字面量化 | #define CHAR(x) #@x | CHAR(a) → 'a' |
應用場景:自動生成變量名、序列化字段名、調試日誌輸出變量名與值。
do { ... } while(0) 慣用法#define SAFE_FREE(ptr) do { \
if (ptr) { \
free(ptr); \
ptr = NULL; \
} \
} while(0)
為何用 do-while(0)?
if-else 懸空 else 問題:if (cond) SAFE_FREE(p); else ... 若宏展開含 ; 會導致 else 匹配失敗。while(0) 保證僅執行一次,編譯器優化後無運行開銷。#ifdef DEBUG
#define LOG(fmt, ...) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)
#else
#define LOG(fmt, ...)
#endif
#ifndef HEADER_H
#define HEADER_H
// 头文件内容
#endif
__FILE__、__LINE__:源文件名、行號,調試定位利器。__DATE__、__TIME__:編譯日期時間。__cplusplus:C++ 標準版本號(如 201703L 表示 C++17)。__STDC__:C 標準合規標識。#define PI 3.1415926
#undef PI // 取消定義,后续代码中 PI 不再被替换
// 數組元素個數
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
// 結構體成員偏移量
#define OFFSET_OF(type, member) ((size_t)&(((type*)0)->member))
// 結構體成員大小
#define SIZE_OF_MEMBER(type, member) sizeof(((type*)0)->member)
// 位運算工具
#define SET_BIT(reg, bit) ((reg) |= (1 << (bit)))
#define CLEAR_BIT(reg, bit) ((reg) &= ~(1 << (bit)))
#define TEST_BIT(reg, bit) ((reg) & (1 << (bit)))
// 避免溢出的飽和加法
#define INC_SAT(val) ((val) = ((val) + 1 > (val)) ? (val) + 1 : (val))
// 2 的冪次取模優化
#define MOD_POW2(val, mod) ((val) & ((mod) - 1)) // mod 必須是 2 的冪
// 字符大小寫轉換
#define TO_UPPER(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 0x20 : (c))
#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
#define IS_HEX(c) (IS_DIGIT(c) || ((c) >= 'A' && (c) <= 'F') || ((c) >= 'a' && (c) <= 'f'))
// I/O 端口訪問
#define INP(port) (*(volatile uint8_t*)(port))
#define OUTP(port, val) (*(volatile uint8_t*)(port) = (uint8_t)(val))
| 宏用途 | 現代 C++ 替代 | 優勢 |
|---|---|---|
| 常量定義 | constexpr / const | 類型安全、作用域約束、調試可見 |
| 類函數宏 | inline 函數 / 模板 | 類型檢查、無副作用風險、支持重載 |
| 條件編譯 | #if / #ifdef(仍需預處理器) | 編譯期裁剪、跨平台無替代方案 |
| 字符串化/粘連 | 無直接替代 | 元編程、代碼生成場景仍需宏 |
宏是雙刃劍:威力巨大但缺乏類型安全,濫用易導致難以診斷的 Bug。遵循原則:能用語言特性解決就不用宏;必須用宏時,全括號包裹、避免副作用參數、善用 do-while(0)、文檔化說明用途。掌握宏機制,讀懂系統頭文件、第三方庫源碼將不再設限。