前置處理器 (Preprocessor) 在編譯器前端執行,進行文字替換、檔案包含、條件編譯。它不懂 C 語法,只認得 `#` 開頭的指令,卻能構建跨平台抽象層、編譯期計算、領域特定語言 (DSL)。善用是神器,濫用成維護噩夢。
| 指令 | 用途 | 關鍵特性 |
|---|---|---|
#include | 包含檔案 | 可包含任意文字檔,不限 .h |
#define | 定義宏/常數 | 支援參數、變參、字串化、連接 |
#undef | 取消定義 | 配合條件編譯切換實作 |
#if / #ifdef / #ifndef | 條件編譯 | 編譯期決定保留哪段代碼 |
#elif / #else / #endif | 分支控制 | 同上 |
#error | 強制編譯錯誤 | 攔截不支援的平台/配置 |
#pragma | 編譯器指示 | 編譯器專屬,影響最佳化/警告/對齊 |
#line | 改變行號/檔名 | 代碼生成工具常用 |
__VA_ARGS__ (C99)#define LOG(fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
LOG("啟動完成"); // 無額外參數
LOG("連線 %s:%d", host, port); // 有額外參數
/* ## 消除前置逗號,支援零參數 */
#define STRINGIFY(x) #x
#define CONCAT(a, b) a##b
STRINGIFY(hello) // 展開為 "hello"
CONCAT(var, 1) // 展開為 var1
do { ... } while(0) 慣用語#define SAFE_FREE(ptr) \
do { free(ptr); (ptr) = NULL; } while(0)
/* 使用時可加分號,不影響 if-else 結構 */
if (err) SAFE_FREE(buf);
else do_something();
static_assert 前身)#define COMPILE_TIME_ASSERT(cond, msg) \
typedef char static_assert_##msg[(cond) ? 1 : -1]
COMPILE_TIME_ASSERT(sizeof(int) == 4, int_must_be_32bit);
#ifdef _WIN32
#define DLLEXPORT __declspec(dllexport)
#include
#elif defined(__linux__)
#define DLLEXPORT __attribute__((visibility("default")))
#include
#else
#error "Unsupported platform"
#endif
/* config.h */
#define ENABLE_LOGGING 1
#define ENABLE_STATS 0
/* code.c */
#if ENABLE_LOGGING
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...) ((void)0)
#endif
編譯時可用 -DENABLE_STATS=1 覆蓋,無需改代碼。
#define VERSION_MAJOR 2
#define VERSION_MINOR 5
#define VERSION_PATCH 1
#define VERSION_STRING "2.5.1-" GIT_COMMIT_HASH
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int x = 5, y = 10;
int z = MAX(x++, y); // x 增加兩次!
修正:C11 用 _Generic + inline 函數,或 GNU 擴展 ({ typeof(a) _a=(a); typeof(b) _b=(b); _a>_b?_a:_b; })。
#define DECLARE_LIST(type) \
typedef struct type##List { type *data; size_t len; } type##List
/* 難以除錯、IDE 無法跳轉、錯誤訊息難懂 */
建議:用 _Generic (C11) 或 C++ 模板;C 語言乖乖寫具體型別。
/* 標準衛兵 */
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* 內容 */
#endif
/* 或 C11 標準 (GCC/Clang/MSVC 均支援) */
#pragma once
/* 追蹤函數進出 */
#define FUNC_ENTER() printf(">> %s:%d\n", __func__, __LINE__)
#define FUNC_LEAVE() printf("<< %s:%d\n", __func__, __LINE__)
/* 印出變數名與值 */
#define DUMP_VAR(x) printf(#x " = %d\n", (x))
/* 編譯期檢查指標非 NULL */
#define ASSERT_PTR(p) _Static_assert(__builtin_constant_p(p) && (p) != NULL, "null ptr")
/* 靜態字串連接 */
#define STR_JOIN(a,b) a##b
| 宏 | 說明 |
|---|---|
__FILE__ | 當前源檔路徑 |
__LINE__ | 當前行號 |
__func__ | 當前函數名 (C99) |
__DATE__, __TIME__ | 編譯日期/時間 |
__STDC_VERSION__ | C 標準版本號 (199901L, 201112L, 201710L) |
__cplusplus | C++ 編譯時定義 |
MYAPP_MAX_CONN 避免衝突。-Wmacro-redefined -Wundef -Wendif-labels。前置處理器是 C 語言的「編譯期運行時」。掌握條件編譯、變參宏、字串化、編譯期斷言,能構建靈活的跨平台架構;避開副作用、型別不安全、除錯困難等坑,才能讓代碼庫長期可維護。原則:**能用 `const`/`enum`/`inline`/`_Generic` 解決的,絕不寫宏。**