在GCC和MSVC中的C++ UTF-8字面量

10

这里有一些简单的代码:

#include <iostream>
#include <cstdint>

    int main()
    {
         const unsigned char utf8_string[] = u8"\xA0";
         std::cout << std::hex << "Size: " << sizeof(utf8_string) << std::endl;
          for (int i=0; i < sizeof(utf8_string); i++) {
            std::cout << std::hex << (uint16_t)utf8_string[i] << std::endl;
          }
    }

在MSVC和GCC中,我看到了不同的行为。

MSVC将"\xA0"视为未编码的Unicode序列,并将其编码为UTF-8。因此,在MSVC中输出为:

C2A0

这正确地编码为UTF-8 Unicode符号U+00A0

但是在GCC的情况下,什么也没有发生。它将字符串视为简单的字节。即使我在字符串文字之前删除u8,也没有变化。

如果将字符串设置为u8"\u00A0";,则两个编译器都使用输出C2A0进行UTF8编码。

为什么编译器表现不同,哪一个才是正确的?

用于测试的软件:

GCC 8.3.0

MSVC 19.00.23506

C++11


1
你在 MSVC 上得到什么输出(附注:我没有它)?在 g++clang 上,我得到了这个:大小: 2 a0 0 - brc-dd
2
u8 的输出是标准的,必须是 UTF-8。但 u8 输入的解释可能不是标准的。\uXXXX\UXXXXXXXX 的行为是标准的,它们必须被解释为一个代码点。但是 \xXX 的解释有点更加实现相关。\xA0 可以被解释为一个单独的 char 0xA0,或者它可以被扩展为代码点 U+00A0 然后编码为 2 个 char 0xC2 0xA0。你会看到这两种行为。 - Remy Lebeau
@RemyLebeau能否提供一些关于"\xA0可能会(..)扩展到码点"的参考资料,因为https://en.cppreference.com/w/cpp/language/escape表示\xnn是一个字节而不是码点。 - Mr Lister
@MrLister cppreference.com通常是可靠的,但它并非绝对可靠。鉴于UTF-8有一些字节序列的有效规则,如果必要的话,编译器可能会应用一些翻译。 - Mark Ransom
2
字符字面值 U8'\xA0' 是不合法的(因为该代码点不能用单个UTF-8代码单元表示),因此我预计带有这种字面值的字符字符串也会导致一个不合法的程序。 - 1201ProgramAlarm
4个回答

3
他们都错了。
据我所知,C++17标准在这里这里表示:
“窄字符串文字的大小是转义序列和其他字符的总数,加上每个通用字符名称的多字节编码至少为一个,再加上终止符“\0”至少为一个。”
虽然有其他提示,但这似乎是转义序列不是多字节且MSVC的行为是错误的最强烈的迹象。
目前有一些票据被标记为正在调查此问题: 然而,它也提到了关于UTF-8字面量的这里
如果该值不能用单个UTF-8代码单元表示,则程序无效。由于0xA0不是有效的UTF-8字符,因此程序不应编译。
请注意:
以u8开头的UTF-8文字面量被定义为狭窄的。 \xA0是转义序列 \u00A0被认为是通用字符名称而不是转义序列。

1
“ill-formed” 意味着程序在未发出诊断的情况下不应被编译。标准允许编译这样的程序,并将此行为称为扩展。 - Paweł Stankowski
1
“这里有一个非常微妙的界限,即‘这是扩展’和‘这是错误’之间。可以说,从标准的角度来看,前者的每个实例都是后者的例子。” - Asteroids With Wings
有关可表示性的评论是针对字符字面量的,而这个问题是关于没有这种限制的字符串字面量。因此不存在冲突,这个答案的后半部分不相关。 - John Meacham
然而,这里也提到了UTF-8字面量:“该引用仅适用于字符字面量,而不适用于字符串字面量。” - user17732522

2

这是CWG问题1656

通过P2029R4,它已经在当前标准草案中得到解决,因此数值转义序列将按其值作为单个代码单元进行考虑,而不是作为Unicode代码点,然后编码为UTF-8。即使这导致无效的UTF-8序列,也是如此。

因此,GCC的行为是/将是正确的。


1
我无法告诉你哪种方式符合标准。MSVC的方法至少在逻辑上是一致的且易于解释。三个转义序列\x、\u和\U的行为相同,除了它们从输入中提取的十六进制数字的数量:2、4或8。每个都定义了一个Unicode代码点,必须将其编码为UTF-8。嵌入一个未编码的字节会导致创建无效的UTF-8序列的可能性。

0
编译器的行为不同是因为它们决定如何实现C++标准:
  • GCC使用严格规则,按照标准实现
  • MSVC使用宽松规则,以更实用的“现实世界”方式实现标准
所以在GCC中失败的事情通常在MSVC中可以工作,因为它更允许。而且MSVC会自动处理其中的一些问题。
这里有一个类似的例子:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=33167。 它遵循标准,但并不是你所期望的。
至于哪个是正确的,取决于你对“正确”的定义。

1
但是GCC并没有按原样实现它。 - Asteroids With Wings

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接