編譯—連結—載入工具鏈

巨集與巨集展開(macro / macro expansion)

/ MAK-roh /

巨集是你用 #define 設定的一段具名文字,使得之後名稱出現的地方,前置處理器都把它替換成你給的文字。巨集展開就是這種替換的發生。它是在編譯前掃過你程式碼的尋找與取代,像是你自己設定的文書軟體自動更正。寫 #define PI 3.14159,之後每個 PI 在交給編譯器的文字裡都會逐字變成 3.14159。

巨集有兩種。物件式巨集是代表某段文字的單純名稱,如 #define BUFSIZE 1024。函式式巨集帶括號與引數,如 #define SQUARE(x) ((x)*(x)),使用時把引數文字貼進樣式裡。關鍵在於這仍是文字貼上,而非真正的函式呼叫:不計算任何值,不先求值任何引數。文字 x 只是被你放在括號裡的東西逐字替換,之後編譯器才讀這個結果。

那種文字本質正是每個經典巨集陷阱的根源。如果你寫 #define SQUARE(x) x*x(沒有括號),那麼 SQUARE(a+b) 會展開成 a+b*a+b,由於運算子優先順序,它根本不是平方。如果你呼叫 SQUARE(i++),i++ 會被貼兩次,i 就被遞增兩次。永遠要把整個本體與每個引數都加上括號,且絕不要傳入有副作用的運算式。對於常數,const int 或 enum 通常比巨集安全,因為它們遵守 C 的型別與範圍規則;對於短小的程式碼,inline 函式能完全避開重複求值的陷阱。

#define SQUARE(x) x*x /* 錯誤:沒有括號 */ z = SQUARE(a+b); /* 展開成:z = a+b*a+b; —— 錯了! */ #define SAFE(x) ((x)*(x)) /* 修正:SAFE(a+b) -> ((a+b)*(a+b)) */

函式式巨集貼的是文字,所以漏掉括號會讓周圍的運算子混進來。

函式式巨集不是函式:它的引數在文字上出現幾次就求值幾次,所以 SQUARE(i++) 會把 i 遞增兩次,這是真正函式絕不會有的錯誤。

又称
#define macro巨集