具有八进制序列的 C++20 UTF-8 字符串文字

Sch*_*eff 3 c++ utf-8 c++20

在将遗留代码移植到 C++20 时,我将字符串文字(带有预期的 UTF-8 编码文本)替换为 UTF-8 字符串文字(前缀为u8)。

\n

因此,我遇到了八进制序列的问题,我过去用它来逐字节编码 UTF-8 序列:

\n

虽然
\n"\\303\\274"是 的正确编码\xc3\xbc,但
\nu8"\\303\\274"最终以\xc3\x83\xc2\xbc.

\n

我对此进行了进一步调查,并在cppreference.com上发现:

\n
\n
    \n
  1. 对于每个数字转义序列,以v由转义序列中的数字序列组成的八进制或十六进制数表示的整数值给出,T 作为字符串文字\xe2\x80\x99s 数组元素类型(参见上表

    \n
      \n
    • 如果v不超过 T 的可表示值的范围,则转义序列贡献值为 的单个代码单元v
    • \n
    \n
  2. \n
\n
\n

(强调我的)

\n

用我自己的话说:在 UTF-8 字符串文字中,八进制 ( \\ooo) 和十六进制 ( \\xXX) 转义序列被解释为 Unicode 代码点,类似于 Unicode 序列 (\\uXXXX\\UXXXXXXXX)。

\n

因此,这对我来说似乎是合理的:对于 UTF-8 字符串文字,Unicode 转义序列应该优于按字节的八进制序列(我过去使用过)。

\n

出于好奇(并且出于演示的目的),我对 coliru 做了一个小测试,并惊讶地发现使用g++ -std=c++20,八进制序列仍然被解释为单个字节。考虑到上面的内容,我得出结论:

\n

MSVC似乎是正确的,而g++是错误的。

\n

我制作了一个 MCVE,并在本地 Visual Studio 2019 中运行:

\n
#include <iostream>\n#include <string_view>\n\nvoid dump(std::string_view text)\n{\n  const char digits[] = "0123456789abcdef";\n  for (unsigned char c : text) {\n    std::cout << \' \'\n      << digits[c >> 4]\n      << digits[c & 0xf];\n  }\n}\n\n#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\\n"; __VA_ARGS__ \n\nint main()\n{\n  DEBUG(const char* const text = "\\344\\270\\255");\n  DEBUG(dump(text));\n  std::cout << \'\\n\';\n  DEBUG(const char8_t* const u8text = u8"\\344\\270\\255");\n  DEBUG(dump((const char*)u8text));\n  std::cout << \'\\n\';\n  DEBUG(const char8_t* const u8textU = u8"\\u4e2d");\n  DEBUG(dump((const char*)u8textU));\n  std::cout << \'\\n\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n

MSVC的输出:

\n
const char* const text = "\\344\\270\\255";\ndump(text);\n e4 b8 ad\nconst char8_t* const u8text = u8"\\344\\270\\255";\ndump((const char*)u8text);\n c3 a4 c2 b8 c2 ad\nconst char8_t* const u8textU = u8"\\u4e2d";\ndump((const char*)u8textU);\n e4 b8 ad\n
Run Code Online (Sandbox Code Playgroud)\n

(请注意,第一个和第三文字的转储是相同的,而第二个转储则通过将每个八进制序列解释为 Unicode 代码点来生成 UTF-8 序列。)

\n

相同的代码在 Compiler Explorer 中运行,使用g++ (13.2)编译:

\n
const char* const text = "\\344\\270\\255";\ndump(text);\n e4 b8 ad\nconst char8_t* const u8text = u8"\\344\\270\\255";\ndump((const char*)u8text);\n e4 b8 ad\nconst char8_t* const u8textU = u8"\\u4e2d";\ndump((const char*)u8textU);\n e4 b8 ad\n
Run Code Online (Sandbox Code Playgroud)\n

相同的代码在 Compiler Explorer 中运行,使用clang (17.0.1)编译:

\n
const char* const text = "\\344\\270\\255";\ndump(text);\n e4 b8 ad\nconst char8_t* const u8text = u8"\\344\\270\\255";\ndump((const char*)u8text);\n e4 b8 ad\nconst char8_t* const u8textU = u8"\\u4e2d";\ndump((const char*)u8textU);\n e4 b8 ad\n
Run Code Online (Sandbox Code Playgroud)\n

编译器资源管理器上的演示

\n

我的结论是否正确,即 MSVC 根据 C++ 标准正确,而不是 g++ 和 clang?

\n
\n

之前通过网络搜索发现:

\n\n
\n

使用十六进制转义序列而不是八进制序列不会改变任何内容:编译器资源管理器上的演示

\n

我更喜欢某种不寻常的八进制序列,因为它们仅限于 3 位数字,没有不相关的字符可能会无意中将它们扩展为 \xe2\x80\x94 ,与十六进制序列相反。

\n
\n

更新:

\n

当我准备为 MSVC 提交错误时,我意识到这已经完成了:
\n unicode 字符串文字中的转义序列被过度编码(不符合 => 编译器错误)

\n

cpp*_*ner 5

用我自己的话说:在 UTF-8 字符串文字中,八进制 ( \ooo) 和十六进制 ( \xXX) 转义序列被解释为 Unicode 代码点,类似于 Unicode 序列 (\uXXXX\UXXXXXXXX)。

不,这是不正确的。在 UTF-8 中,代码单元表示 8 位单元(= 字节),Unicode代码点由一个或多个代码单元的序列表示。每个八进制转义序列对应于一个代码单元,这与对应于代码的 Unicode 转义序列不同。

所以GCC和Clang是正确的,而MSVC是错误的。