善用前置處理器巨集是 C 語言高手的分水嶺。本文系統整理 20 類實戰巨集模式,涵蓋型別可移植性、記憶體映射存取、邊界計算、編譯期斷言、除錯追蹤、副作用防護 等核心場景,並說明 # 字串化與 ## 連接運算子的進階用法與陷阱。
#ifndef COMDEF_H
#define COMDEF_H
// 標頭檔內容
#endif
現代替代:#pragma once (GCC/Clang/MSVC 全支援),更簡潔無命名衝突風險。
// 推薦:使用 (C99 標準)
#include
uint8_t u8; int8_t i8;
uint16_t u16; int16_t i16;
uint32_t u32; int32_t i32;
uint64_t u64; int64_t i64;
// 專案自定義別名 (若禁用 stdint.h)
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned long uint32;
typedef signed char int8;
typedef signed short int16;
typedef signed long int32;
// 避免使用 byte/word/dword 等模糊命名
#define MEM_B(addr) (*(volatile uint8_t *)(addr))
#define MEM_W(addr) (*(volatile uint16_t *)(addr))
#define MEM_DW(addr) (*(volatile uint32_t *)(addr))
// 使用範例
#define REG_CTRL 0x40001000
MEM_W(REG_CTRL) = 0x1234;
uint16_t val = MEM_W(REG_CTRL);
關鍵:volatile 防止編譯器優化掉「看似多餘」的讀寫;強制轉型避免嚴格別名規則違規。
#define MAX(a,b) ({ typeof(a) _a=(a), _b=(b); _a>_b?_a:_b; }) // GCC/Clang Statement Expression
#define MIN(a,b) ({ typeof(a) _a=(a), _b=(b); _a<_b?_a:_b; })
// 標準 C 版 (C11 _Generic 或雙重求值風險較小的簡易版)
#define MAX_SIMPLE(a,b) ((a)>(b)?(a):(b)) // 注意:參數有副作用時會求值兩次
最佳實務:參數無副作用時用簡易版;有副作用或泛型需求時用內建函數 fmax/fmin 或 C++ std::max。
#define OFFSETOF(type, member) ((size_t)&(((type *)0)->member))
#define FIELD_SIZEOF(type, member) (sizeof(((type *)0)->member))
// 標準庫 已提供 offsetof,優先使用
#include
offsetof(struct Foo, bar);
// Little-Endian 兩位元組合成 16-bit
#define LE16_TO_U16(ptr) ((uint16_t)(ptr)[0] | ((uint16_t)(ptr)[1] << 8))
#define U16_TO_LE16(ptr, val) do { (ptr)[0] = (val) & 0xFF; (ptr)[1] = (val) >> 8; } while(0)
// Big-Endian (網路序)
#define BE16_TO_U16(ptr) (((uint16_t)(ptr)[0] << 8) | (ptr)[1])
#define U16_TO_BE16(ptr, val) do { (ptr)[0] = (val) >> 8; (ptr)[1] = (val) & 0xFF; } while(0)
// 標準網路序函數 (可移植)
#include
uint16_t htons(uint16_t); uint16_t ntohs(uint16_t);
uint32_t htonl(uint32_t); uint32_t ntohl(uint32_t);
#define BIT(n) (1U << (n))
#define BIT_MASK(n) ((1U << (n)) - 1)
#define GET_BITS(val, lo, hi) (((val) >> (lo)) & BIT_MASK((hi)-(lo)+1))
#define SET_BITS(val, lo, hi, newv) ((val) & ~(BIT_MASK((hi)-(lo)+1) << (lo))) | ((newv) << (lo))
// 向上對齊到 alignment (2 的冪次)
#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
// 陣列元素個數 (僅適用靜態陣列)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
// x % (2^n) 等價於 x & (2^n - 1)
#define MOD_POW2(x, n) ((x) & ((1U << (n)) - 1))
#define INB(port) (*(volatile uint8_t *)(port))
#define INW(port) (*(volatile uint16_t *)(port))
#define INDW(port) (*(volatile uint32_t *)(port))
#define OUTB(port, val) (*(volatile uint8_t *)(port) = (uint8_t)(val))
#define OUTW(port, val) (*(volatile uint16_t *)(port) = (uint16_t)(val))
#define OUTDW(port, val) (*(volatile uint32_t *)(port) = (uint32_t)(val))
// x86 實模式/保護模式需用 inline asm: inb/outb 指令
__FILE__ // 當前源檔名
__LINE__ // 當前行號
__DATE__ // 編譯日期 "Mmm dd yyyy"
__TIME__ // 編譯時間 "hh:mm:ss"
__func__ // 當前函數名 (C99)
__STDC__ // 標準 C 合規性
__STDC_VERSION__ // 199409L / 199901L / 201112L
#ifdef DEBUG
#define DBG(fmt, ...) fprintf(stderr, "[DBG %s:%d %s] " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define ASSERT(cond) do { if(!(cond)) { fprintf(stderr,"ASSERT %s:%d %s\n",__FILE__,__LINE__,#cond); abort(); } } while(0)
#else
#define DBG(fmt, ...) do{}while(0)
#define ASSERT(cond) do{}while(0)
#endif
// 使用範例
DBG("buffer=%p len=%zu", buf, len);
ASSERT(ptr != NULL);
do{...}while(0) 與 Statement Expression// 錯誤示範
#define BAD_SWAP(a,b) { int t=a; a=b; b=t; }
if (x>y) BAD_SWAP(x,y); else ... // 編譯錯誤:else 無匹配 if
// 正確:do-while(0) 形成單一語句
#define SWAP(a,b) do { int t=a; a=b; b=t; } while(0)
// GCC/Clang 專屬:Statement Expression 可回傳值
#define MAX_VAL(a,b) ({ typeof(a) _a=(a), _b=(b); _a>_b?_a:_b; })
int m = MAX_VAL(x++, y++); // x++, y++ 各求值一次
# 與連接 ## 進階應用#param → 將參數轉為字串字面常數 "param"。a##b → 將兩側記號連接成單一記號 ab。#define _STR(x) #x
#define STR(x) _STR(x)
#define _CONCAT(a,b) a##b
#define CONCAT(a,b) _CONCAT(a,b)
#define VALUE 42
STR(VALUE) // 展開為 "42" (非 "VALUE")
CONCAT(var, _suffix) // var_suffix
// 靜態斷言 (C11 _Static_assert 更佳)
#define STATIC_ASSERT(cond, msg) typedef char static_assert_##msg[(cond)?1:-1]
STATIC_ASSERT(sizeof(int) == 4, int_must_be_32bit);
// 匿名變數 (利用 __LINE__ 唯一性)
#define ANONYMOUS_VAR(type, prefix) type prefix##_##__LINE__
ANONYMOUS_VAR(int, tmp); // int tmp_123;
// 結構體初始化輔助:自動填入欄位名字串
#define FILL_ENUM(e) {e, #e}
enum Cmd { CMD_START, CMD_STOP };
struct { Cmd id; const char *name; } cmd_map[] = {
FILL_ENUM(CMD_START), // {CMD_START, "CMD_START"}
FILL_ENUM(CMD_STOP)
};
#define _TYPE_BUF_SIZE(t) sizeof(#t)
#define TYPE_BUF_SIZE(t) _TYPE_BUF_SIZE(t)
char buf[TYPE_BUF_SIZE(INT_MAX)]; // INT_MAX 展開為 2147483647 → sizeof("2147483647") = 11
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static const char THIS_FILE[] = GET_FILE_NAME(__FILE__); // 編譯期常數
stdint.h, stddef.h, arpa/inet.h, _Static_assert)。#define ADD(a,b) ((a)+(b)) 防運算優先級錯誤。do{}while(0) 形成單一語句,支援後接分號與 else。MAX(i++, j++) 導致雙重增量;改用函數或 Statement Expression。MYPROJ_,避免與系統頭檔衝突。掌握這些模式,能讓巨集從「難以維護的魔法」轉化為「型別安全、零開銷、可除錯的編譯期元程式工具」。