X 巨集(X-macro)
/ EKS-MAK-roh /
想像你有一份清單——錯誤碼、列舉值、暫存器名稱——而你需要把同一份清單用在好幾種形式:一個列舉、一個名稱字串陣列、一個 switch。手動同步這些副本正是那種會逐漸過時的雜務。X 巨集是一種前置處理器樣式,把清單只寫「一次」並從中產生各種形式。
你把清單定義成一個巨集,對每個項目呼叫一個佔位符(慣例上叫 X)一次:#define COLORS(X) X(RED) X(GREEN) X(BLUE)。接著要產生某種形式,你就定義 X 的意義、展開清單、再取消定義 X。要做列舉,你寫 #define X(name) name,然後 COLORS(X),在列舉中得到 RED, GREEN, BLUE;要做名稱表,你重新定義 #define X(name) #name,同樣的 COLORS(X) 產生字串 "RED", "GREEN", "BLUE"。單一來源清單驅動每種產生的形式,所以在一處加入一個顏色就同時更新了列舉與表格。
它在「平行的多份表格必須保持一致」之處很有用:運算碼表、協定欄位清單、命令分派器、序列化程式碼。誠實的取捨:X 巨集重度依賴前置處理器,所以可能難讀也難除錯(展開結果是隱形的,除非你用像 gcc -E 的指令請編譯器傾印它),而現代替代方案包括程式碼產生器,或在適合時改用 _Generic 與指定初始化子。
#define COLORS(X) X(RED) X(GREEN) X(BLUE) enum color { COLORS(GEN_ENUM) }; // 搭配 #define GEN_ENUM(n) n, static const char *names[] = { COLORS(GEN_STR) }; // #define GEN_STR(n) #n, // 一份清單 -> 同時產生列舉與名稱表
同一份 COLORS 清單,用兩種不同的 X 定義展開,產生彼此匹配、永不脫節的列舉與字串表。
X 巨集讓平行表格保持同步,但難讀也難除錯;產生的文字是隱形的,除非你傾印前置處理器輸出(例如 gcc -E)。