CWKSC's blog | 博客

重掃描

Featured image

目錄: C/C++ Macro 宏系列 | CWKSC’s blog | 博客

▌重掃描

重掃描是宏之中一個很重要的概念和技巧

很多出奇的用法都是靠它完成

這裏我們先直接引用標準 § 19.3.4

如果看不懂的話可以粗略看一下,然後看後面的例子

1 After all parameters in the replacement list have been substituted and # and ## processing has taken place, all placemarker preprocessing tokens are removed. Then the resulting preprocessing token sequence is rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.


2 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. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.


3 The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one, but all pragma unary operator expressions within it are then processed as specified in 19.9 below.

翻譯:

1 在替換列表中的所有參數都已經替換並且 ### 處理已完成後,所有 Placemarker 預處理 Token 都將被刪除。然後,將重新掃描所得的預處理 Token 序列以及源文件的所有後續預處理 Token,以替換更多的宏名稱。

2 如果一個已經被替換過的宏在替換列表掃描期間找到(不包括源文件的其餘預處理 Token),則不會替換該宏。此外,如果任何嵌套的替換遇到已經被替換過的宏,也不會替換它。這些不可替換的宏名稱預處理 Token 不再可用於進一步替換,即使它們稍後在已被替換的情況下(重新)檢查了該宏名稱預處理 Token 也是如此。

3 最終由宏完全替換的 Token 和預處理指令一樣也不會進行處理,即使它們很相似,但是其中的所有 pragma 一元運算符表達式將按照以下 19.9 中的說明進行處理。

雖然我覺得翻譯已經說得很清楚,不過還是可以再縮短和簡潔一些

▌例子

#define merge(a, b) a ## b
#define another_marco 42

merge(another, _marco) // [1]
another ## _marco      // [2] 
another_marco          // [3] 處理 ## 完成,準備重掃描
42                      // [4] 重掃描完成

在替換和 ### 的處理完成後,會重掃描 Token 序列

[3],替換和 ## 的處理已經完成,準備重掃描

[4],重掃描發生了,掃描到 another_marco,替換成 42

▌例子2

#define get_args0(args0, ...) args0
#define foo(...) get_args0(__VA_ARGS__)

foo(x, y)
get_args0(__VA_ARGS__)
get_args0(x, y)  // x, y 被視為一個整體
x, y

Visual Studio 在處理 __VA_ARGS__ 時會視為一個整體的 Token 來處理

foo(x, y) 展開了 x, y

我們預期的是展開為 x 而不是 x, y

我們可以用重掃描來解決這個問題:

#define get_args0(args0, ...) args0
#define expend(...) __VA_ARGS__
#define boo(...) expend(get_args0(__VA__ARGS__))

boo(x, y)                          // [1]
expend(get_args0(__VA__ARGS__))  // [2]
expend(get_args0(x, y))          // [3] x, y 被視為一個整體
__VA_ARGS__                       // [4]
get_args0(x, y)                  // [5] x, y 不被視為整體
args0                             // [6]
x                                 // [7]

▌這裏會說得比較詳細

可以看到宏的展開次序(但或許不對

boo(x, y)  // [1]
//expend(get_args0(__VA__ARGS__))  // [2]

[1] ,掃描到 boo ,展開

//boo(x, y)  // [1]
expend(get_args0(__VA__ARGS__)) // [2]

掃描宏參數,沒有找到

掃描 __VA_ARGS__ ,替換 __VA_ARGS__x, y

//expend(get_args0(__VA__ARGS__)) // [2]
expend(get_args0(x, y))  // [3]

(這時候 x, y 被視為一個整體)

掃描 object-like 宏,沒有找到

掃描 ### ,沒有找到

沒有找到可以再次替換的宏和 ### 處理

因此替換和 ### 處理完成,重掃描 Token 序列

掃描到 expend,展開

//expend(get_args0(x, y))  // [3]
__VA_ARGS__  // [4]

掃描宏參數,沒有找到

掃描 __VA_ARGS__,替換 __VA_ARGS__get_args0(x, y)

// __VA_ARGS__  // [4]
get_args0(x, y)  // [5]

(這時候 x, y 被視為整體,get_args0(x, y) 被視為整體)

掃描 object-like 宏,沒有找到

掃描 ### ,沒有找到

沒有找到可以再次替換的宏和 ### 處理

因此替換和 ### 處理完成,重掃描 Token 序列

掃描到 get_args0,展開

//get_args0(x, y)  // [5]
args0  // [6]

掃描宏參數,找到 args0,替換為 x

//args0  // [6]
x  // [7]

掃描 __VA_ARGS__ ,沒有找到

掃描 object-like 宏,沒有找到

掃描 ### ,沒有找到

沒有找到可以再次替換的宏和 ### 處理

因此替換和 ### 處理完成,重掃描 Token 序列

重掃描期間什麽都沒有找到

結束處理

▌結語

雖然在上面的例子很明確地指出了展開次序

但是這或許不對

我還沒有找到在標準文檔中,對宏展開次序更加詳細的明確指定

如果未來學習到的話會回來修改

或者開一篇新文章講宏的展開次序

CWKSC

Author 作者

CWKSC

喜歡編程,會一點點鋼琴,會一點點畫畫,喜歡使用顏文字 About me 關於我

For any comments or discussions on my blog post, you can open an issue here

對於我博客文章的任何評論或討論,可以在這裏開一個 issue

Feel free to give your comments. OW<