#define 是 C 語言預處理階段的指令,純粹進行文字替換,不參與編譯、不計算、不檢查型別。理解這一點,就能避開 90% 的宏陷阱。
#define PI 3.1415926 #define BUFFER_SIZE 4096 #define APP_NAME "MyApp"
預處理器將後續程式碼中出現的 PI 直接替換為 3.1415926。
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define DEBUG_LOG(fmt, ...) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)
注意:#define NAME (params) body 中,宏名與左括號之間不能有空格,否則會被視為物件式宏。
C 程式編譯流程:
源碼 (.c) → 預處理器 (cpp) → 展開 #include、#define、#ifdef... → 修改後源碼 (.i) → 編譯器 (cc1) → 編譯為組合語言 (.s) → 組譯器 (as) → 目的檔 (.o) → 連結器 (ld) → 執行檔
關鍵點:宏展開發生在編譯前,純文字替換,無語意分析、無型別檢查、無運算。
#define N 2+2 int a = N * N; // 展開為 2+2*2+2 = 2+4+2 = 8 ❌ (預期 16) // 解法:整個替換文本加括號 #define N (2+2) int a = N * N; // (2+2)*(2+2) = 16 ✅
#define SQUARE(x) x * x int y = SQUARE(2+2); // 2+2*2+2 = 8 ❌ int z = SQUARE(2+2)/SQUARE(2+2); // (2+2*2+2)/(2+2*2+2) = 8/8 = 1 ❌ (預期 1) // 解法:參數加括號 + 整體加括號 #define SQUARE(x) ((x) * (x)) // SQUARE(2+2) → ((2+2) * (2+2)) = 16 ✅ // SQUARE(2+2)/SQUARE(2+2) → ((2+2)*(2+2))/((2+2)*(2+2)) = 1 ✅
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int i = 0;
int m = MAX(i++, 5); // 展開: ((i++) > (5) ? (i++) : (5)) → i 增加兩次!❌
// 解法:改用 inline 函式 (C99) 或 GNU statement expression (GCC/Clang)
static inline int max_int(int a, int b) { return a > b ? a : b; }
// 或 GCC/Clang 專用:
#define MAX(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a > _b ? _a : _b; })
#define STR(x) #x #define CONCAT(a, b) a ## b STR(hello) → "hello" CONCAT(var, 1) → var1
實用除錯宏:
#define DPRINT(expr) printf(#expr " = %d\n", expr)
DPRINT(x/y); → printf("x/y = %d\n", x/y);
| 優點 | 說明 | 代價 |
|---|---|---|
| 維護性 | 常數集中定義,修改一處全局生效 | 除錯時看不到宏名 (需用 gcc -E 查看展開後碼) |
| 效能 (簡單操作) | 避免函式呼叫開銷 (保存/恢復堆疊幀) | 程式碼膨脹、指令快取壓力 |
| 型別無關 | 同一宏可用於 int、float、指標等 | 型別不安全,易誤用 |
| 條件編譯 | #ifdef、#if 配合宏開關功能 | 增加程式碼複雜度 |
const 或 enum:有型別、有作用域、可除錯。
const double PI = 3.1415926;
enum { BUFFER_SIZE = 4096 };
static inline (C99) 或 __inline (MSVC):型別安全、無副作用、可除錯、編譯器最佳化同等效果。
static inline int square(int x) { return x * x; }
() 包裹() 包裹do { ... } while(0) 包裹 (避免 if/else 懸空)MYLIB_MAX) 避免污染命名空間#undef:在區域作用域結束前取消宏,減少干擾。#define SWAP(t, a, b) do { t tmp = (a); (a) = (b); (b) = tmp; } while(0)
// 使用: SWAP(int, x, y);
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define BIT_SET(val, bit) ((val) |= (1U << (bit))) #define BIT_CLEAR(val, bit) ((val) &= ~(1U << (bit))) #define BIT_TEST(val, bit) (((val) >> (bit)) & 1U)
#define COMPILE_ASSERT(cond, msg) typedef char msg[(cond) ? 1 : -1] COMPILE_ASSERT(sizeof(int) == 4, int_must_be_32bit);
除錯宏展開問題時,使用編譯器輸出預處理後的原始碼:
gcc -E source.c -o source.icl /E source.c 或 cl /P source.c (產生 .i 檔)查看 source.i 即可看到所有宏被展開後的真實樣貌。
#define 是把雙面刃:用得好能簡化程式碼、提升效能;用不好會製造難以追蹤的 Bug。現代 C (C99/C11) 已提供 const、enum、static inline、_Static_assert 等更安全的替代方案,**能不用宏就不用,必須用時嚴守括號法則、避免副作用、控制作用域**。