为什么这个简单的代码不能一致地编译?

bar*_*bas 3 c++ g++ visual-c++ clang++ c++14

以下代码在 g++、clang 和 Visual Studio 上编译:

#define HEX(hex_)  0x##hex_
int main()
{
    return HEX(BadC0de);
}
Run Code Online (Sandbox Code Playgroud)

与此修改一样,使用 C++14 数字分隔符:

    return HEX(1'Bad'C0de);
Run Code Online (Sandbox Code Playgroud)

但这不能在 g++ 或 clang 上编译(它可以在 Visual Studio 上运行):

#define HEX(hex_)  0x##hex_
int main()
{
    return HEX(A'Bad'C0de);
}
Run Code Online (Sandbox Code Playgroud)

g++ 输出:

<source>:4:1: warning: multi-character character constant [-Wmultichar]
    4 |     return HEX(A'Bad'C0de);
      | ^  
<source>: In function 'int main()':
<source>:4:17: error: expected ';' before user-defined character literal
    4 |     return HEX(A'Bad'C0de);
      |                 ^~~~~~~~~
<source>:1:25: note: in definition of macro 'HEX'
    1 | #define HEX(hex_)   0x##hex_
      |                         ^~~~
<source>:4:17: error: unable to find character literal operator 'operator""C0de' with 'int' argument
    4 |     return HEX(A'Bad'C0de);
      |                 ^~~~~~~~~
<source>:1:25: note: in definition of macro 'HEX'
    1 | #define HEX(hex_)   0x##hex_
      |                         ^~~~
Run Code Online (Sandbox Code Playgroud)

更新:有趣的是,预处理器输出是

    return 0xA'Bad'C0de;
Run Code Online (Sandbox Code Playgroud)

它确实可以编译,因此显然独立预处理器的工作方式与统一预处理器不同。

这在 g++/clang 上也会失败,但会出现不同的错误:

    return HEX(Bad'C0de);
Run Code Online (Sandbox Code Playgroud)

g++ 输出:

<source>:4:19: warning: missing terminating ' character
    4 |     return HEX(Bad'C0de);
      |                   ^
<source>:5:2: error: unterminated argument list invoking macro "HEX"
    5 | }
      |  ^
<source>: In function 'int main()':
<source>:4:12: error: 'HEX' was not declared in this scope
    4 |     return HEX(Bad'C0de);
      |            ^~~
<source>:4:15: error: expected ';' at end of input
    4 |     return HEX(Bad'C0de);
      |               ^
      |               ;
<source>:4:15: error: expected '}' at end of input
<source>:3:1: note: to match this '{'
    3 | {
      | ^
Run Code Online (Sandbox Code Playgroud)

更新:在这种情况下,预处理器在解析 HEX() 参数之前停止。

我愿意相信这是一个 g++ bug,但考虑到 Visual Studio 预处理器在历史上的不合规性有多严重,也许这只是一厢情愿的想法。事实上,最后一个程序不仅在 g++ 上失败,还在Visual Studio 上触发内部编译器错误(至少在 godbolt.org 上)!

msvc 输出:

<source>(4): error C2001: newline in constant
<source>(4): fatal error C1057: unexpected end of file in macro expansion
Internal Compiler Error in Z:\opt\compiler-explorer\windows\19.00.24210\bin\amd64\cl.exe.  You will be prompted to send an error report to Microsoft later.
INTERNAL COMPILER ERROR in 'Z:\opt\compiler-explorer\windows\19.00.24210\bin\amd64\cl.exe'
    Please choose the Technical Support command on the Visual C++
    Help menu, or open the Technical Support help file for more information
Run Code Online (Sandbox Code Playgroud)

天真地,我希望所有编译器都将所有文本传递给宏替换,然后再尝试解释其含义(毕竟它是一个预处理器!);只有在 ## 连接之后,我才会期望检查标记的含义。(是的,我知道一些基本的解析恰好匹配括号、方括号等,因此它们中的逗号不会分割参数,但我不希望它扩展到任何其他语言结构。)

标准对这些程序有什么规定吗?它们是否在某种程度上不符合规范,或者它们是否合法并且编译器存在错误?

Chr*_*odd 8

这是规范中令人讨厌的漏洞之一。预处理器(在规范中)根据“预处理标记”进行定义。输入首先被分成一系列预处理标记,然后对该序列进行宏处理。

现在的问题来自这样一个事实:0xA'Bad'C0de它是单个预处理标记,但A'Bad'C0de不是——它是三个预处理标记(A、'Bad'和C0de),并且标记粘贴运算符##被定义为仅粘贴两个相邻的标记。在这种情况下,标记化阶段取决于已定义的宏以及它们可能执行的操作。

解决这个问题需要进行重大的规范更改,并且需要跟踪直接相邻的预处理标记与非直接相邻的标记(它们之间有空格或注释的标记),并让操作员在有意义的情况下粘贴##其他直接相邻的标记。

这仍然会遇到诸如以下问题HEX(A'B):您如何判断何时)应该是多字符字符常量标记的一部分,而不是结束宏参数列表?