Som*_*ken 13 c++ c-preprocessor c++03
考虑以下代码:
#define A -100
//later..
void Foo()
{
int bar = -A;
//etc..
}
Run Code Online (Sandbox Code Playgroud)
现在,这可以在我测试的一些主要编译器(MSVC,GCC,Clang)上很好地进行编译,并且bar == 100按预期进行,这是因为所有这些编译器的预处理器在标记之间插入了一个空格,因此您最终得到:
int bar = - -100;
Run Code Online (Sandbox Code Playgroud)
因为我希望代码尽可能地可移植,所以我去检查此行为是否由标准定义,但是我在此上找不到任何东西。这种行为是由标准保证的还是仅仅是编译器功能,是否还bar = --100;允许幼稚的方法(显然不会进行编译)?
这是用以下语言指定的:这两个-字符不会以串接形式形成--运算符。
通过必须解析源文件的方式来确保没有连接:在转换阶段4中执行宏扩展。在此转换阶段之前,在转换阶段3中,必须按一系列预处理标记和空白[ lex.phases] / 3:
源文件被分解为预处理令牌和空白字符序列(包括注释)。源文件不应以部分预处理令牌或部分注释结尾。13每个注释都用一个空格字符代替。换行符被保留。未指定是否保留除换行符以外的每个非空序列的空白字符或将其替换为一个空格字符。
因此,在翻译阶段3之后,靠近bar定义的标记序列可能如下所示:
// here {...,...,...} is used to list preprocessing tokens.
{int, ,bar, ,=, ,-,A,;}
Run Code Online (Sandbox Code Playgroud)
在第4阶段之后,您将获得:
{int, ,bar, ,=, ,-,-, ,100,;}
Run Code Online (Sandbox Code Playgroud)
从概念上讲,在阶段7中删除了空间:
{int,bar,=,-,-,100,;}
Run Code Online (Sandbox Code Playgroud)
一旦在翻译的早期将输入拆分为预处理令牌,使两个相邻的预处理令牌合并为一个令牌的唯一方法就是预处理器的##运算符。这就是##运算符的作用。这就是为什么有必要的原因。
预处理完成后,适当的编译器将根据预先解析的预处理令牌分析代码。适当的编译器将不会尝试将两个相邻标记合并为一个标记。
在您的示例中,内部-和外部-是两个不同的预处理标记。它们不会合并为一个--令牌,并且编译器也不会将它们视为一个--令牌。
例如
#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;`
}
Run Code Online (Sandbox Code Playgroud)
这就是语言规范定义行为的方式。
在实际的实现中,预处理阶段和编译阶段通常彼此分离。预处理阶段的输出通常以纯文本形式表示(而不是作为令牌的某些数据库)。在这样的实现中,预处理器和编译器必须就如何分离相邻(“触摸”)预处理令牌达成某种约定。通常,预处理器会在两个单独的标记之间插入一个额外的空间,这些标记恰好在源代码中“接触”。
该标准确实说明了有关该额外空间的任何内容,并且正式不应该存在该额外空间,但这只是通常在实践中实现这种分隔的方式。
请注意,由于该空间“不应存在”,因此此类实现还必须做出一些努力以确保在其他情况下该“额外空间”是“不可检测的”。例如
#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`
}
Run Code Online (Sandbox Code Playgroud)
的这两行都main应该输出--i。
因此,要回答您的原始问题:是的,从某种意义上说,您的代码是可移植的,在符合标准的实现中,这两个-字符永远不会成为--。但是实际的空间插入只是实现细节。其他一些实现可能会使用另一种技术来防止这些应用-合并为--。