解析C函數式宏

來源:互聯網
上載者:User

原文地址:http://hi.baidu.com/bellgrade/blog/item/391c1b2b8cd932325343c1b9.html

從一個相對簡單的例子說起吧。

view plainprint?
  1. #define f(a,b) a##b  
  2.       #define g(a)    #a  
  3.       #define h(a) g(a)  
  4.        h(f(1,2))  
  5.        g(f(1,2))  

相信不少人都見過這個例子。我們不妨再仔細分析一下它的解析過程。應該是這樣的:

對於g(f(1,2)),前置處理器看到的先是g,然後是(,說明這是一個函數式的宏,好,然後替換後面的實參f(1, 2),得到#f(1,2)(註:直接這麼寫非法,這裡只是為了表示方便而已),因為它前面有個#,所以下一步是不會替換f的參數的!所以進一步得到"f(1, 2)",解析結束。而對於h(f(1,2)),前置處理器看到的先是h,然後(,對其參數f(1, 2)進行替換,得到g(f(1,2)),注意這裡的下一步是,前置處理器就繼續往後走,處理剛得到的f(1,2),而不是回過頭去再處理g!得到12,到了這裡我們的得到的是一個:g(12),然後重新掃描整個宏,替換g,最後得到"12"。

標準第6.10.3.1節中對此描述的還比較清晰,它這樣寫道:

After the arguments for the invocation of a function-like macro have been
identified, argument substitution takes place. A parameter in the replacement
list, unless preceded by a # or ## preprocessing token or followed by a ##
preprocessing token (see below), is replaced by the corresponding argument
after all macros contained therein have been expanded.

注意加粗的部分。到了在這裡,我們可以簡單總結一下函數式宏的基本替換流程:

首先要識別出這是一個函數式宏,通過什嗎?通過調用中出現的(,沒錯是左括弧!到這裡後下一步是參數替換,就是根據該宏的定義把實參全部替換進去,然後接著向後走,除非是遇到了#和##(正如上面例子中的g),把後面替換後的東西中如果還有已知宏的話,進行替換或者同樣的展開,直到解析到末尾:所有的參數都已經替換完(或者#或##已經處理完);最後,前置處理器還會對整個宏再進行一次掃描,因為前一輪替換中有可能在前面替換出一些新的東西來(比如上面例子中的h)。

這裡咋看之下沒什麼問題,其實問題很多!為什嗎?因為宏替換不僅允許發生在“調用”宏的時候,而且還發生在它定義時!

問題1:宏的名字本身會被替換嗎?

這個問題也可以這樣問:宏允許被重新定義嗎?不允許,但是允許相同的重新定義。標準這樣寫道:

An identifier currently defined as an object-like macro shall not be
redefined by another #define preprocessing directive unless the second definition
is an object-like macro definition and the two replacement lists are identical.
Likewise, an identifier currently defined as a function-like macro shall not be
redefined by another #define preprocessing directive unless the second definition
is a function-like macro definition that has the same number and spelling of
parameters, and the two replacement lists are identical.

問題2:宏的參數(形參)會被替換嗎?

先舉個例子說明這個問題:

#define foo 1
#define bar(foo) foo + 2
bar(a)

我們是得到a+2還是1+2?a+2!因為形參是不會被替換掉的,你想想啊,如果形參都被替換掉了這個宏就沒什麼作用了!那實參呢?實參會的,因為實參的替換髮生在傳遞這個參數之前:

Before being substituted, each argument’s preprocessing tokens are
completely macro replaced as if they formed the rest of the preprocessing file

問題3:宏中參數之外的符號會被替換嗎?

會,上面提到過“after all macros contained therein have been expanded”,也就是說這個發生在參數替換之前。但是,這裡有個非常詭異的問題:如果被替換出來的符號正好和形參一樣怎麼辦?就像下面這個例子:

#define foo bar
#define baz(bar) bar + foo
baz(1)

我們會得到1+1還是1+bar?後者,因為替換出來的那個bar是不會計算在形參之內的,雖然標準並沒有明確這一點。想想吧,如果是的話那個宏的定義也會被破壞了!

另一個例子:

#define foo bar
#define mac(x) x(foo)
mac(foo)

根據上面所說,我們首先得到foo(foo),然後foo再被替換成bar,最後得到bar(bar)。

好了,到這裡我們終於可以看一下更複雜的例子了:

#define m !(m)+n
#define n(n) n(m)
m(m)

這個例子相當複雜,是我見過的最複雜的一個宏。:-) 剛看到我們可能都有點蒙,沒關係,咱們一步一步地來。

第一步很好走,第一個m直接被替換,得到:!(m)+n(m),別猶豫,接著往下走,替換最後一個m,得到:!(m)+n(!(m)+n),這時這一遍掃描已經完成。到這裡我們得提出另外一個東西才能繼續,你可能知道,遞迴。標準對此的描述是:

If the name of the macro being replaced is found during this scan of the
replacement list (not including the rest of the source file’s preprocessing
tokens), it is not replaced.

在上次替換中,被替換的是m,所以m在這裡的再次出現將不會被替換,所以下一步是會替換第一個n,得到:!(m)+!(m)+n(m),注意這裡又替換出一個新的m來,這個m會被替換,因為這次掃描���沒完成!下一步得到:!(m)+!(m)+n(!(m)+n),第二遍掃描結束,全部的替換完成。

綜上,我們可以總結出兩條重要的宏替換規則:1)再複雜的宏也只是被掃描兩遍,而且遞迴是不允許發生的,即使在第2遍時;2)一個替換完成後如果還沒掃描完,要從被替換的那裡繼續。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.