前陣子仔細重新研究了一下C的宏展開。總結起來,有以下幾個主要規則:
- 每次宏展開的結果會被重復掃描,直到沒有任何可展開的宏為止。
- 每展開一個宏,都會記住這次展開,在這個宏展開的結果及其后續展開中,不再對相同的宏做展開。
- 帶參數的宏,先對參數做展開,除非宏定義體中包含#或##
a) #表示將后續標識符轉換為字符串
b) ##表示將兩個標識符連接成一個標識符
c) 注意參數展開的結果中即使有逗號(,),也不視為參數的分隔符 - 如果宏定義中帶有參數,而代碼中出現同樣標識符時沒有參數,不視為宏。
下面的三段代碼分別解釋了2, 3, 4. 注釋中描述了宏每一步展開的細節
這段代碼主要解釋規則2.(~表示已經被展開過)
#define foo foo bar
#define bar bar bar foo
#define foo2(a) bar2(a,foo2) foo2(a) (a)
#define bar2(a, b) foo2(a) bar2(a,b) (a) (b)
foo
// |-> foo bar
// | |~ |-> bar bar foo
// |-> foo bar bar foo (至此,所有符號都已展開過)
bar
// |-> bar bar foo
// | |~ |~ |-> foo bar
// |-> bar bar foo bar (至此,所有符號都已展開過)
foo bar
// foo bar
// | |
// |-> foo bar bar bar foo
// | |~ | |~ |~ |
// |-> foo bar bar foo bar bar foo bar
foo2(1)
// |-> bar2(1, foo2) foo2(1) (1)
// | | |~
// |-> foo2(1) bar2(1, foo2) (1) (foo2) foo2(1) (1)
bar2(1, 1)
// |-> foo2(1) bar2(1,1) (1) (1)
// | | |~
// |-> bar2(1,foo2) foo2(1) (1) bar2(1,1) (1) (1)
這段代碼主要解釋規則3.
#define foo vfoo
#define bar vbar
#define foo2(a) #a
#define foo3(a, b) a ## _ ## b
#define foo20(a) foo2(a)
#define foo30(a, b) foo3(a, b)
#define foo40(x) foo30(x)
#define x x1,x2
foo2(foo)
// -> "foo" ('#'阻止了參數展開,如果需要展開參數,定義另一個宏,見foo20)
foo3(foo, bar)
// -> foo_bar ('##'阻止了參數展開,如果需要展開參數,定義另一個宏,見foo30)
foo20(foo)
// | |-->vfoo (foo20帶有一個參數,匹配宏定義,先展開參數)
// | |
// |-> foo2(vfoo)
// |-> "vfoo"
foo30(foo, bar)
// | |-->vfoo, |->vbar
// | | |
// |-> foo3(vfoo, vbar)
// |-> vfoo_vbar
foo30(x)
// 錯誤,參數個數不匹配, x中的逗號不視為參數分隔符。如果需要將這個逗號作為分隔符,定義另一個宏,見foo40
foo40(x)
// | |-> x1,x2
// |-> foo30(x1,x2) //這個時候,逗號視為合法分隔符。
// |-> foo3(x1,x2)
// |-> x1_x2
這段代碼主要解釋規則4.
#define foo(a) foo=a(x)
#define bar(a) (bar=a)
#define foo2(a) foo=a(x,y)
#define bar2(a,b) bar(a*b)
foo
// 參數個數不匹配,不認為是宏
bar
// 同上
foo(bar)
// |-> foo=bar(x) (bar無參數,不認為是宏)
// |-> foo=(bar=x) (此次掃描,bar符合宏定義)
foo2(bar2)
// |-> foo=bar2(x,y) (bar2無參數,不認為是宏)
// |-> foo=bar(x*y) (此次掃描,bar符合宏定義)
// |-> foo=(bar=x*y)