【发布时间】:2019-03-03 09:51:13
【问题描述】:
我正在 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;
}
打印到stdout的结果很奇怪:
(X3_VALUE + 1)
3
在 gcc 上尝试相同的操作时,构建失败(预期)。
当注释掉cout << TOTAL << endl; 时,我得到了something different:
(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。
我尝试的另一件事是在 Visual Studio 中使用 /P 编译器选项编译此代码,以获取预处理代码:
int main() {
cout << "(X3_VALUE + 1)" << endl;
cout << (((0 + 1) + 1) + 1) << endl;
return 0;
}
- 那么,正如我所怀疑的,它是 Visual Studio 预处理器中的一个错误吗?还是合法的未定义行为?
- 也许这种行为可以被滥用来真正递归地扩展宏?我知道有限的递归is possible with some tricks 但仅限于预定义的扫描次数。在这种情况下,我没有观察到
NEXT扩展次数的限制。
【问题讨论】:
-
这可能不是问题,但是以下划线开头后跟大写字母的名称(
_UNDERSCORE、_STRINGIFY)和包含两个连续下划线的名称保留用于实现。不要在你的代码中使用它们。 -
我会从避免使用保留标识符开始。
_后跟一个大写字母保留给实现任何使用。 -
@PeteBecker 我将更改问题以避免下划线后跟大写字母。问题依旧。
-
这是行为 #6。这可以追溯到大约 1983 年,早在 C89 确定预期行为之前。早期的 K&R 预处理器对此并不一致,他们赌错了。可能得益于它必须能够在 96KB 的 RAM 中运行。他们决定不破坏现有客户的代码库,这是微软遭受的臭名昭著的痛苦,最近一直在改变。用 [visual-c++] 标记这样的问题通常是避免不相关 cmets 的好方法。
标签: visual-studio visual-c++ visual-studio-2015 c-preprocessor