为何含C++14数字分隔符的宏代码编译结果不一致?
可正常编译的代码案例
以下代码可在g++、clang和Visual Studio中正常编译:
#define HEX(hex_) 0x##hex_ int main() { return HEX(BadC0de); }
使用C++14数字分隔符的修改版本同样能通过编译:
return HEX(1'Bad'C0de);
编译失败的代码案例1
以下代码无法在g++或clang中编译,但可在Visual Studio中运行:
#define HEX(hex_) 0x##hex_ int main() { return HEX(A'Bad'C0de); }
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_ | ^~~~
更新:有趣的是,这段代码的预处理器输出为:
return 0xA'Bad'C0de;
该输出本身可以正常编译,显然独立预处理器与统一预处理器的行为存在差异。
编译失败的代码案例2
以下代码同样无法在g++/clang中编译,且错误信息与上例不同,甚至会在Visual Studio中触发内部编译器错误:
return HEX(Bad'C0de);
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 | { | ^
更新:此情况下预处理器在解析HEX()参数前就停止工作了。
Visual Studio的错误输出:
<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
C++标准的相关规定
根据C标准,预处理器处理宏参数时,并非简单将参数视为纯文本,而是需要先进行令牌化——把输入分解为符合C语法的令牌(如标识符、字符常量、数字等)。也就是说,预处理器在处理宏调用时,会先尝试将参数解析为合法的预处理令牌序列,再执行替换操作。
对于HEX(A'Bad'C0de),预处理器会将A'Bad'C0de解析为标识符A、多字符常量'Bad'、用户定义字面量后缀C0de,这并非单一合法令牌,因此在宏替换前就触发语法错误。而预处理器输出的0xA'Bad'C0de是合法的带分隔符十六进制整数,所以能正常编译,这说明整合了预处理与编译阶段的统一预处理器,会更早进行令牌合法性检查,而独立预处理器可能仅做文本替换。
对于HEX(Bad'C0de),未闭合的单引号导致预处理器无法完成令牌化,直接判定宏参数列表未终止,进而引发后续错误,这完全符合标准——未闭合的字符常量本身就是语法错误,预处理器无法继续处理。
结论:这些代码不符合C++标准,并非编译器bug。Visual Studio能编译部分代码是因为其预处理器历史上存在不符合标准的实现行为,而触发内部编译器错误则属于其实现缺陷。
内容的提问来源于stack exchange,提问作者barnabas

