[英]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.