預處理器在編譯器介入前掃描源碼,執行文本級替換。它不理解 C 語法,只識別以 # 開頭的指令。輸出經處理的源碼交由編譯器。
| 指令 | 功能 |
|---|---|
#include | 包含文件 |
#define | 定義宏 |
#undef | 取消宏 |
#if / #elif / #else / #endif | 條件編譯 (常量表達式) |
#ifdef / #ifndef | 宏定義檢查 |
#error | 強制報錯 |
#pragma | 編譯器特定指示 |
#line | 修改行號/文件名 |
#include 機制詳解
#include <stdio.h> // 系統路徑:/usr/include, 編譯器內建路徑
#include "myhead.h" // 用戶路徑:當前目錄 → 系統路徑
最佳實踐:標準庫用 <>,項目頭文件用 ""。
// myhead.h
#ifndef MYHEAD_H
#define MYHEAD_H
// 內容...
#endif
現代替代:#pragma once (非標準但廣泛支持)。
標準要求至少支持 8 層嵌套,現代編譯器通常 200+。
#define 的藝術與陷阱
#define MAX_SIZE 1024
#define PI 3.1415926535
#define DEBUG 1
// 使用
int buf[MAX_SIZE];
#if DEBUG
printf("Debug mode\n");
#endif
約定:宏名全大寫,區別於變量。
#define SQUARE(x) ((x) * (x))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define STR(s) #s // 字符串化
#define CAT(a,b) a##b // 符號粘合
// 錯誤示範
#define SQUARE(x) x * x
int y = SQUARE(3+2); // 展開 3+2 * 3+2 = 3+6+2 = 11 (錯!)
// 正確:參數與整體雙重括號
#define SQUARE(x) ((x) * (x))
// 展開 ((3+2) * (3+2)) = 25 ✓
// 危險:副作用雙重求值
int i = 5;
int z = SQUARE(i++); // ((i++) * (i++)) → 未定義行為!
// 解決:改用 inline 函數或避免副作用參數
# 與 符號粘合 ##
// # 將參數變為字符串字面量
#define LOG(msg) printf("[LOG] %s:%d " #msg "\n", __FILE__, __LINE__)
LOG(Starting server); // 展開 printf(... "Starting server" ...)
// ## 合成標識符
#define DECLARE_VAR(type, name) type var_##name
DECLARE_VAR(int, count); // int var_count;
#define MAKE_FUNC(name) void func_##name() { printf(#name "\n"); }
MAKE_FUNC(init); // void func_init() { printf("init\n"); }
do-while(0) 慣用法
// 錯誤:if 條件下分號導致邏輯斷裂
#define SWAP(a,b) { int t=a; a=b; b=t; }
if (x > y) SWAP(x,y); else ... // 錯誤!
// 正確:do-while(0) 包裹為單一語句
#define SWAP(a,b) do { int t=a; a=b; b=t; } while(0)
#define ENABLE_NETWORK 1
#define ENABLE_SSL 0
#if ENABLE_NETWORK
init_network();
#if ENABLE_SSL
init_ssl();
#endif
#endif
#ifdef _WIN32
#define PATH_SEP '\\'
#elif defined(__linux__)
#define PATH_SEP '/'
#else
#error "Unsupported platform"
#endif
#if defined(__GNUC__) && __GNUC__ >= 4
#define ATTR_UNUSED __attribute__((unused))
#else
#define ATTR_UNUSED
#endif
| 宏 | 含義 |
|---|---|
__FILE__ | 當前文件名 |
__LINE__ | 當前行號 |
__DATE__ | 編譯日期 "Mmm dd yyyy" |
__TIME__ | 編譯時間 "hh:mm:ss" |
__STDC__ | C 標準合規性 (1=是) |
__STDC_VERSION__ | C 版本 (199409L, 199901L, 201112L, 201710L) |
__func__ | 當前函數名 (C99, 非宏) |
| 場景 | 傳統宏 | 現代替代 (C99/C11/C++) |
|---|---|---|
| 常量 | #define N 100 | enum { N = 100 }; 或 const int N = 100; |
| 函數式 | #define MAX(a,b)... | inline int max(int a,int b){...} 或 _Generic (C11) |
| 編譯期斷言 | #if... | _Static_assert(expr, msg) (C11) |
| 可變參數 | #define LOG(...) | void log(const char*, ...) + __VA_ARGS__ (C99) |
# 行。const/enum/inline/_Static_assert 替代宏,獲得類型安全與調試可見性。#/##/do-while(0) 進階技巧,編寫健壯宏庫。