[英]Macro expansion with unary minus
請考慮以下代碼:
#define A -100
//later..
void Foo()
{
int bar = -A;
//etc..
}
現在,這可以很好地編譯我測試的一些主要編譯器(MSVC,GCC,Clang)和bar == 100
,這是因為所有這些編譯器的預處理器在令牌之間插入一個空格,所以你最終得到:
int bar = - -100;
因為我希望我的代碼盡可能便攜,所以我去檢查這個行為是否由標准定義,但我找不到任何內容。 標准是否保證了這種行為,或者這只是一個編譯器功能而且是天真的方法(顯然無法編譯) bar = --100;
也允許?
這是在指定的語言:兩個-
字符不會結束-Up在被連接起來形成一個--
運營商。
必須解析源文件的方式確保不存在連接:在轉換階段4執行宏擴展。在轉換階段之前,在轉換階段3期間,源文件必須在預處理標記和空格序列中進行轉換[ lex.phases] / 3 :
源文件被分解為預處理標記和空白字符序列(包括注釋)。 源文件不應以部分預處理令牌或部分注釋結束.13每個注釋都替換為一個空格字符。 保留換行符。 是否保留除了換行符之外的每個非空白字符序列是否由一個空格字符保留或替換是未指定的。
因此,在翻譯階段3之后,靠近條形定義的標記序列可能如下所示:
// here {...,...,...} is used to list preprocessing tokens.
{int, ,bar, ,=, ,-,A,;}
然后在第4階段之后你會得到:
{int, ,bar, ,=, ,-,-, ,100,;}
在第7階段概念性地刪除了空間:
{int,bar,=,-,-,100,;}
一旦在轉換的早期階段將輸入拆分為預處理令牌 ,使兩個相鄰的預處理令牌合並為一個令牌的唯一方法是預處理器的##
運算符。 這是##
運算符的用途。 這就是必要的原因。
預處理完成后,編譯器將根據預解析的預處理標記分析代碼。 編譯器本身不會嘗試將兩個相鄰的令牌合並為一個令牌。
在您的例子內-
和外-
是兩個不同的預處理標記。 他們不會合並成一個--
令牌,他們將不會被編譯器適合作為一個可以看到--
令牌。
例如
#define M1(a, b) a-b
#define M2(a, b) a##-b
int main()
{
int i = 0;
int x = M1(-, i); // interpreted as `int x = -(-i);`
int y = M2(-, i); // interpreted as `int y = --i;`
}
這是語言規范定義行為的方式。
在實際實現中,預處理階段和編譯階段通常彼此分離。 預處理階段的輸出通常以純文本形式表示(而不是某些令牌數據庫)。 在這樣的實現中,預處理器和編譯器本身必須就如何分離相鄰(“觸摸”)預處理令牌的一些約定達成一致。 通常,預處理器將在兩個單獨的令牌之間插入額外的空間,這些令牌碰巧在源代碼中“觸摸”。
該標准確實對該額外空間說了什么,並且正式地說它不應該存在,但這正是這種分離通常在實踐中如何實現的。
請注意,由於該空間“不應該在那里”,因此這些實現還必須付出一些努力來確保在其他環境中“不可檢測”這個額外空間。 例如
#define M1(a, b) a-b
#define M2(a, b) a##-b
#define S_(x) #x
#define S(x) S_(x)
int main()
{
std::cout << S(M1(-, i)) << std::endl; // outputs `--i`
std::cout << S(M2(-, i)) << std::endl; // outputs `--i`
}
兩條main
都應該輸出--i
。
因此,要回答你原來的問題:是的,你的代碼是可移植在某種意義上,在一個符合標准的實現這兩個-
字符永遠不會成為一個--
但實際插入空間只是一個實現細節。 其他一些實現可能會使用不同的技術來防止這些-
從合並到--
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.