什么决定了c++中Unicode字符串的规范化形式

What determines the normalized form of a Unicode string in C++?

本文关键字:规范化 字符串 Unicode 决定了 c++ 什么      更新时间:2023-10-16

在c++中创建字符串文字时,我想知道字符串是如何编码的——我可以指定编码形式(UTF-8、16或32),但我想知道编译器如何确定编码中未指定的部分。

对于UTF-8,字节顺序是不相关的,我假设UTF-16和UTF-32的字节顺序是默认的系统字节顺序。这就剩下了标准化。例如:

std::string u8foo = u8"Föo";
std::u16string u16foo = u"Föo";
std::u32string u32foo = U"Föo";

在这三种情况下,至少有两种可能的编码——分解或组合。对于更复杂的字符,可能有多种可能的编码,但我假设编译器将生成一种规范化形式。

这个假设安全吗?我可以提前知道u8foou16foo中的文本是如何规范化存储的吗?我能不能指定一下?

我的印象是这不是由标准定义的,它是特定于实现的。GCC如何处理它?其他的编译器吗?

对基本源字符集之外的字符串的解释取决于实现。(标准报价如下。)所以没有明确的答案;实现甚至没有义务接受基本字符集以外的源字符。

规范化涉及到可能多个源代码点到可能多个内部代码点的映射,包括重新排序源字符序列的可能性(例如,如果变音符号不符合规范顺序)。这样的转换比标准所期望的源代码内部转换更复杂,我怀疑尝试它们的编译器不会完全符合标准。在任何情况下,我知道没有编译器这样做。

所以,一般来说,你应该确保你提供给编译器的源代码是按照你想要的规范化形式进行规范化的,如果这对你很重要的话。

在GCC的特殊情况下,编译器根据默认语言环境的编码解释源代码,除非另有说明(使用-finput-charset命令行选项)。它将在必要时重新编码为Unicode码点。但它不会改变码点的顺序。如果你给它一个标准化的UTF-8字符串,那就是你得到的。如果你给它一个非规范化字符串,那也是你得到的。

在coliru的这个例子中,第一个字符串被组合,第二个字符串被分解(尽管它们都是某种规范化形式)。(第二个示例字符串的彩色呈现似乎与浏览器有关。在我的机器上,chrome可以正确地呈现它们,而firefox则将变音符号向左移了一个位置。YMMV。)


c++标准将基本源字符集(在§2.3/1中)定义为字母,数字,五个空白字符(空格,换行符,制表符,垂直制表符和formfeed)和符号:

_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = ,  " ’ 

它给了编译器很大的自由度来解释输入,以及如何处理基本源字符集之外的字符。& section;2.2段落1(来自c++ 14 draft n4527):

必要时,以实现定义的方式将物理源文件字符映射到基本源字符集(为行尾指示符引入新行字符)。接受的物理源文件字符集是由实现定义的。任何不在基本源字符集(2.3)中的源文件字符都被指定该字符的通用字符-name替换。(实现可以使用任何内部编码,只要在源文件中遇到的实际扩展字符,以及在源文件中作为通用字符名称表示的相同扩展字符(例如,使用uXXXX表示法)被等效处理,除非这种替换被还原为原始字符串字面值。)

值得补充的是,从c++标准的角度来看,变音符号是字符。因此合成的ñ (u00d1)是一个字符,分解后的ñ(u006e u0303)是两个字符,不管你怎么看。

仔细阅读标准中的上述段落,可以发现不允许严格按照1-1进行规范化或其他转换,尽管编译器可能会拒绝包含基本源字符集以外字符的输入。

Microsoft Visual c++将保留源文件中使用的规范化。

做这个跨平台的主要问题是确保编译器使用正确的编码。MSVC是这样处理的:

源文件编码

编译器必须以正确的编码读取源文件。

MSVC没有在命令行上指定编码的选项,而是依赖于BOM来检测编码,因此它可以读取以下编码:

  • 带BOM的UTF-16,如果文件以该BOM开头
  • UTF-8,如果文件以"xefxbbxbf" (UTF-8"BOM")开头
  • 在所有其他情况下,根据系统语言设置使用ANSI代码页读取文件。实际上,这意味着你只能在源文件中使用ASCII字符。
输出编码

你的unicode字符串在写入到你的可执行文件之前会被编码成字节字符串。

宽字面值(L"...")总是写成UTF-16。

MSVC 2010您可以使用#pragma execution_character_set("utf-8")char字符串编码为UTF-8。默认情况下,它们在本地代码页中编码。这个pragma显然在MSVC 2012中丢失了,但在MSVC 2013中又回来了。

#pragma execution_character_set("utf-8")
const char a[] = "ŦεŞŧ";

对Unicode字面值(u"..."和友元)的支持是在MSVC 2015中刚刚引入的。