【问题标题】:Preprocessor inconsistencies in Visual StudioVisual Studio 中的预处理器不一致
【发布时间】: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 &lt;&lt; TOTAL &lt;&lt; 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


【解决方案1】:

正如 Hans 在 cmets 中提到的,MSVC 预处理器不符合要求。

您可以使用-experimental:preprocessor 启用一致性预处理器。

这是一个简化的复制 + 解决方案:https://godbolt.org/z/7u_-bH

【讨论】:

  • 正如 Hans 在 cmets 中很好解释的那样,这确实是一种 MSVC 不合格行为。您提出了一种使用-experimental:preprocessor 来阻止它的方法,但是,这不是我的问题。我的问题是 1)它是 MSVC 中的错误吗?(是的,正如 Hans 解释的那样)2)可以滥用这种行为来真正递归地扩展宏吗?(对于这个问题我还没有得到答案,也许答案是否定的)。
  • AFAIR 它不会递归地扩展宏。它只做 2 次传球。
  • 如果它只通过 2 次,那么在我上面的示例中,3 的扩展结果如何作为 TOTAL 的扩展?在我看来,NEXT 必须递归扩展 3 次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-15
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多