在将遗留代码移植到 C++20 时,我将字符串文字(带有预期的 UTF-8 编码文本)替换为 UTF-8 字符串文字(前缀为u8
)。
因此,我遇到了八进制序列的问题,我过去用它来逐字节编码 UTF-8 序列:
虽然
"\303\274"
是 的正确编码ü
,但
u8"\303\274"
最终为ü
。
我对此进行了进一步调查,并在cppreference.com上发现:
(强调我的)
用我自己的话说:在 UTF-8 字符串文字中,八进制 ( \ooo
) 和十六进制 ( \xXX
) 转义序列被解释为 Unicode 代码点,类似于 Unicode 序列 (\uXXXX
和\UXXXXXXXX
)。
因此,这对我来说似乎是合理的:对于 UTF-8 字符串文字,Unicode 转义序列应该优于按字节的八进制序列(我过去使用过)。
出于好奇(并且出于演示的目的),我对 coliru 做了一个小测试,并惊讶地发现使用g++ -std=c++20
,八进制序列仍然被解释为单个字节。考虑到上面的内容,我得出结论:
MSVC似乎是正确的,而g++是错误的。
我制作了一个 MCVE,并在本地 Visual Studio 2019 中运行:
#include <iostream>
#include <string_view>
void dump(std::string_view text)
{
const char digits[] = "0123456789abcdef";
for (unsigned char c : text) {
std::cout << ' '
<< digits[c >> 4]
<< digits[c & 0xf];
}
}
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
DEBUG(const char* const text = "\344\270\255");
DEBUG(dump(text));
std::cout << '\n';
DEBUG(const char8_t* const u8text = u8"\344\270\255");
DEBUG(dump((const char*)u8text));
std::cout << '\n';
DEBUG(const char8_t* const u8textU = u8"\u4e2d");
DEBUG(dump((const char*)u8textU));
std::cout << '\n';
}
MSVC的输出:
const char* const text = "\344\270\255";
dump(text);
e4 b8 ad
const char8_t* const u8text = u8"\344\270\255";
dump((const char*)u8text);
c3 a4 c2 b8 c2 ad
const char8_t* const u8textU = u8"\u4e2d";
dump((const char*)u8textU);
e4 b8 ad
(请注意,第一个和第三个文字的转储是相同的,而第二个转储则通过将每个八进制序列解释为 Unicode 代码点来生成 UTF-8 序列。)
相同的代码在 Compiler Explorer 中运行,使用g++ (13.2)编译:
const char* const text = "\344\270\255";
dump(text);
e4 b8 ad
const char8_t* const u8text = u8"\344\270\255";
dump((const char*)u8text);
e4 b8 ad
const char8_t* const u8textU = u8"\u4e2d";
dump((const char*)u8textU);
e4 b8 ad
相同的代码在 Compiler Explorer 中运行,使用clang (17.0.1)编译:
const char* const text = "\344\270\255";
dump(text);
e4 b8 ad
const char8_t* const u8text = u8"\344\270\255";
dump((const char*)u8text);
e4 b8 ad
const char8_t* const u8textU = u8"\u4e2d";
dump((const char*)u8textU);
e4 b8 ad
我的结论是否正确,即 MSVC 根据 C++ 标准正确,而不是 g++ 和 clang?
之前通过网络搜索发现:
使用十六进制转义序列而不是八进制序列不会改变任何内容:Compiler Explorer 上的演示。
我更喜欢某种不寻常的八进制序列,因为它们仅限于 3 位数字,没有不相关的字符可能会无意中扩展它们 - 与十六进制序列相反。
更新:
当我准备为 MSVC 提交错误时,我意识到这已经完成了:
unicode 字符串文字中的转义序列被过度编码(不符合要求 => 编译器错误)
不,这是不正确的。在 UTF-8 中,代码单元表示 8 位单元(= 字节),Unicode代码点由一个或多个代码单元的序列表示。每个八进制转义序列对应于一个代码单元,这与对应于代码点的 Unicode 转义序列不同。
所以GCC和Clang是正确的,而MSVC是错误的。