許多中階 C++ 程式設計師常掛嘴邊:「C 巨集是萬惡之首,應被廢棄」。但 Bjarne Stroustrup 只說 C++ 在特定場合提供更好方案,從未完全否定巨集價值。這句話傳訛成謬,實則是「不會用、不了解」者將責任推給工具。本文舉四例展示巨集無可替代的優雅用法。
網路程式常見 mbuf 結構層層巢狀:
struct mbuf {
struct m_hdr mhdr;
union {
struct {
struct pkthdr MH_pkthdr;
union {
struct m_ext MH_ext;
char MH_databuf[MHLEN];
} MH_dat;
} MH;
char M_databuf[MLEN];
} M_dat;
};
存取最內層 MH_databuf 需寫 M_dat.MH.MH_dat.MH_databuf,冗長且難讀。實際上這些欄位邏輯上都是 mbuf 的屬性,可「壓扁」視之。原始碼定義巨集映射:
#define m_pkthdr M_dat.MH.MH_pkthdr
#define m_pktdat M_dat.MH.MH_dat.MH_databuf
// ...
使用時直接寫 m_pktdat,簡潔明瞭。
VC 執行期庫原始碼大量使用條件巨集處理平台差異:
#ifndef _CRTAPI1
#if _MSC_VER >= 800 && _M_IX86 >= 300
#define _CRTAPI1 __cdecl
#else
#define _CRTAPI1
#endif
#endif
#ifndef _SIZE_T_DEFINED
typedef unsigned int size_t;
#define _SIZE_T_DEFINED
#endif
// ...
無巨集則需撰寫大量平台專屬程式碼,維護成本極高。
MFC 以巨集實現訊息 ID 與處理函式的關聯:
#define DECLARE_MESSAGE_MAP() ...
#define BEGIN_MESSAGE_MAP(theClass, baseClass) ...
#define ON_COMMAND(id, memberFxn) { WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };
幾行巨集展開為完整的訊息映射陣列,極大簡化框架使用者代碼。
16x16 黑白圖示以十六進位陣列儲存難以辨識:
static unsigned short stopwatch[] = { 0x07C6, 0x1FF7, ... };
定義巧妙巨集將二進位圖樣「畫」在原始碼中:
#define X )*2+1 // 1 亮
#define _ )*2 // 0 暗
#define s ((((((((((((((((0
重寫陣列:
static unsigned short stopwatch[] = {
s _ _ _ _ _ X X X X X _ _ _ X X _ ,
s _ _ _ X X X X X X X X X _ X X X ,
// ... 畫出沙漏圖示
s _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
};
程式碼即文檔,一目瞭然。用畢記得 #undef 避免污染後續代碼。
水能載舟,亦能覆舟。巨集是雙面刃,關鍵在於理解其展開機制、副作用與作用域。善用巨集可解決語言機制無法觸及的問題(程式碼生成、跨平台抽象、語法糖),濫用則製造除錯噩夢。掌握好這把利刃,讓它為你服務。