我正在Visual Studio 2015(更新3(中编译以下C++代码:
#include <iostream>
using namespace std;
////////////////////////////////////////
#define UNDERSCORE1(a,b) a ## _ ## b
#define UNDERSCORE(a,b) UNDERSCORE1(a,b)
#define STRINGIFY1(x) #x
#define STRINGIFY(x) STRINGIFY1(x)
#define VALUE(x) UNDERSCORE(x, VALUE)
#define NEXT(x) (VALUE(x) + 1)
/////////////////////////////////////////
#define X1_VALUE 0
#define X2_VALUE NEXT(X1)
#define X3_VALUE NEXT(X2)
#define TOTAL NEXT(X3)
int main() {
cout << STRINGIFY(TOTAL) << endl;
cout << TOTAL << endl;
return 0;
}
打印到标准输出的结果非常奇怪:
(X3_VALUE + 1)
3
在 gcc 上尝试相同的操作时,构建失败(预期(。
当注释掉cout << TOTAL << endl;
时,我得到了完全不同的东西:
(NEXT(X2) + 1)
实际上 gcc 行为是有意义的,因为NEXT
宏是递归调用的:NEXT(X3)
扩展到X3_VALUE
,而又扩展到NEXT(X2)
,所以不执行NEXT
宏的第二次展开(NEXT(X2)
(。
没有意义的是Visual Studio的行为:
- 当使用
STRINGIFY
打印宏TOTAL
时,NEXT
似乎扩展了两次以产生X3_VALUE
。 - 编译宏
TOTAL
直接发送到cout
时,NEXT
一路扩展!好像预处理器运行多次以递归扩展NEXT
.
我尝试的另一件事是使用/P
编译器选项在 Visual Studio 中编译此代码,以获取预处理的代码:
int main() {
cout << "(X3_VALUE + 1)" << endl;
cout << (((0 + 1) + 1) + 1) << endl;
return 0;
}
- 那么,正如我所怀疑的那样,它是Visual Studio预处理器中的错误吗?还是合法的未定义行为?
- 也许这种行为可以被滥用来真正递归地扩展宏?我知道通过一些技巧可以实现有限的递归,但仅限于预定义的扫描次数。在这种情况下,我没有观察到
NEXT
扩展次数的限制。
正如Hans在评论中提到的,MSVC预处理器不符合要求。
您可以使用-experimental:preprocessor
启用一致性预处理器。
下面是一个简化的重现 + 解决方案: https://godbolt.org/z/7u_-bH