宏分為兩種,一種是 object-like 宏,比如:
#define STR "Hello, World!"
另一種是 function-like 宏,比如:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
對於 function-like 宏,定義時的參數叫 Parameters,比如上面宏 MIN 的參數 X、Y,當調用時,傳遞的參數叫 Arguments。
宏參數 Arguments
當給宏傳遞參數 Arguments 時,可以不用全部傳遞,比如:
MIN(a, b) -> ((a) < (b) ? (a) : (b)) // 完全傳遞 MIN(, b) -> (() < (b) ? () : (b)) // 只傳遞后一個 MIN(a, ) -> ((a) < () ? (a) : ()) // 只傳遞前一個 MIN(,) -> (() < () ? () :()) // 這種允許 MIN(, ,) // 報錯,接收2個參數,傳遞了3個 MIN() // 報錯,接收2個參數,傳遞了1個
宏參數 Arguments 的擴展
當向宏中傳遞參數 Arguments 時,在參數替換到宏里面之前,首先要對參數 Arguments 進行完全的擴展,當參數擴展完畢之后,才將最終擴展的結果替換到宏里面,同時再對整個宏進行擴展。比如調用宏 MIN(MIN(a, b), c),那么首先第一個參數 MIN(a, b)要進行擴展,擴展的結果為 ((a) < (b) ? (a) : (b)),由於第2個參數 c 不用擴展,也就是第1步得到的擴展結果為:
MIN(((a) < (b) ? (a) : (b)), c)
接下來進行第2步擴展,得到的結果為:
((((a) < (b) ? (a) : (b))) < (c) ? (((a) < (b) ? (a) : (b))) : (c))
這樣做的一個好處是為了防止宏的嵌套調用,比如有下面一個宏定義:
#define f(x) x
如果有如下調用 f(f(1)),假設第一步不將參數 f(1) 完全展開,就會出現 f(f(1)) 被展開為 f(1),由於間接的自引用,宏不再繼續展開,此時得到的結果就為 f(1)。而有了2步擴展,第一步參數 f(1) 被擴展成 1,然后替換后繼續擴展,可以得到結果就是1。
自引用宏
一個宏在定義時,如果宏名出現在了宏定義中,那么就是一個自引用宏,比如:
#define foo (4 + foo)
如果調用這個宏 foo,按照上面兩步展開的過程,那么將會是一個無限循環的過程。首先 foo 展開為 (4 + foo),然后 (4 + foo) 中的 foo 繼續展開成 (4 + foo) 成為 (4 + (4 + foo)),如此繼續下去。但是實際上,對於自引用宏,只擴展1次,后面不會再做擴展,也就是說調用宏 foo,最終的擴展結果就是 (4 + foo)。
對於自引用宏的一種特殊情況就是間接自引用,比如有如下宏定義:
#define x (4 + y) #define y (2 * x)
當調用宏 x 時,首先擴展為 (4 + y),然后對 y 進行擴展,結果為 (4 + (2 * x)),如果此時繼續對 x 進行擴展,那么就會無限遞歸,為了處理這種情況,擴展會在這一步終止,也就是最終結果是 (4 + (2 * x))。
當調用宏 y 時一樣,首先擴展為 (2 * x),然后對 x 進行擴展,結果為 (2 * (4 + y)),擴展到這一步也會停止。
參數 Arguments 有字符串化(#)和連接(##)操作
在宏擴展過程當中,當參數 Arguments 有字符串化(#)和連接操作(##)時,參數的擴展就沒有第1步。比如有下面宏定義:
#define AFTERX(x) X_ ## x
#define XAFTERX(x) AFTERX(x)
#define TABLESIZE 1024
#define BUFSIZE TABLESIZE
當調用 AFTERX(BUFSIZE) 時,由於參數 BUFSIZE 有連接操作(##),那么它就不會繼續擴展成 TABLESIZE,TABLESIZE 繼續擴展成1024,而是直接使用,也就是最終的擴展結果是 X_BUFSIZE,而不是 X_10。如果想要得到 X_10 的結果,需要做1次間接宏調用,重新定一個宏 XAFTERX 來調用宏 AFTERX,當調用宏 XAFTERX(BUFSIZE) 時,就會得到結果 X_10。