c++ u8-literals应该如何工作?

bmp9r5qi  于 2023-07-01  发布在  其他
关注(0)|答案(3)|浏览(82)

在理解u8-literals的语义上有困难,或者更确切地说,理解g++ 4.8.1上的结果
这是我的期望:

const std::string utf8 = u8"åäö"; // or some other extended ASCII characters
assert( utf8.size() > 3);

这是在g++ 4.8.1上的结果

const std::string utf8 = u8"åäö"; // or some other extended ASCII characters
assert( utf8.size() == 3);
  • 源文件为ISO-8859(-1)
  • 我们使用这些编译器指令:-m64 -std=c++11 -pthread -O3 -fpic

在我的世界里,不管源文件的编码如何,生成的utf8字符串都应该大于3。
或者,我完全误解了u8的语义,以及它所针对的用例?请你开导我。

更新

如果我像许多人建议的那样显式地告诉编译器源文件的编码是什么,我就得到了u8字面量的预期行为。但是,常规文字也被编码为utf8
即:

const std::string utf8 = u8"åäö"; // or some other extended ASCII characters
assert( utf8.size() > 3);
assert( utf8 == "åäö");
  • 编译器指令:g++ -m64 -std=c++11 -pthread -O3 -finput-charset=ISO8859-1
  • 尝试了一些从iconv定义的其他字符集,例如:ISO_8859-1等...

我现在比以前更困惑了。。

k4ymrczo

k4ymrczo1#

u8前缀实际上只是意味着“在编译这段代码时,从这个字面量生成一个UTF-8字符串”。它没有说明编译器应该如何解释源文件中的文字。
所以你有几个因素在起作用:
1.哪个编码是源文件编写的(在您的情况下,显然是ISO-8859)。根据这种编码,字符串文字是“åäö”(3个字节,包含值0xc 5,0xe 4,0xf 6)
1.编译器在阅读源文件时采用的编码是什么?(我怀疑GCC默认为UTF-8,但我可能错了。
1.编译器用于对象文件中生成的字符串的编码。您可以通过u8前缀将其指定为UTF-8。

20102;是出了问题的地方。如果编译器将源文件解释为ISO-8859,那么它将读取这三个字符,将它们转换为UTF-8,并写入这些字符,结果给你一个6字节(我认为这些字符中的每个字符编码为UTF-8中的2字节)字符串。

但是,如果它假设源文件是UTF-8,那么它根本不需要进行转换:它读取3个字节,它假设是UTF-8(即使它们是UTF-8的无效垃圾值),并且由于您要求输出字符串也是UTF-8,因此它只输出相同的3个字节。
您可以告诉GCC使用-finput-charset采用哪种源代码编码,或者可以将源代码编码为UTF-8,或者可以在字符串文字中使用\uXXXX转义序列(例如,\u00E5而不是å

编辑:

为了澄清一点,当您在源代码中指定带有u8前缀的字符串文字时,您正在告诉编译器“无论您在 * 阅读 * 源文本时使用哪种编码,请在将其写入目标文件时将其转换为UTF-8”。你没有说应该如何解释源文本。这要由编译器来决定(可能基于您传递给它的标志,可能基于进程的环境,或者可能只是使用硬编码的默认值)
如果源文本中的字符串包含字节0xc 5,0xe 4,0xf 6,* 和 ,你告诉它“源文本编码为ISO-8859”,那么编译器将识别出“字符串由字符组成”。它将看到u8前缀,并将这些字符转换为UTF-8,将字节序列0xc 3,0xa 5,0xc 3,0xa 4,0xc 3,0xb 6写入目标文件。在本例中,您最终得到一个有效的UTF-8编码文本字符串,其中包含字符“åäö”的UTF-8表示形式。
但是,如果源文本中的字符串包含相同的字节,
并且您使编译器相信源文本编码为UTF-8*,则编译器可能会做两件事(取决于实现:

  • 它可能会尝试将字节解析为UTF-8,在这种情况下,它将识别出“这不是一个有效的UTF-8序列”,并发出错误。这就是Clang所做的。
  • 或者,它可能会说“好的,我这里有3个字节,我被告知假设它们构成一个有效的UTF-8字符串。我会抓住他们,看看会发生什么”。然后,当它应该将字符串写入目标文件时,它会说“好的,我有之前的3个字节,标记为UTF-8。这里的u8前缀意味着我应该将这个字符串写成UTF-8。酷,那就不需要转换了。我只写这3个字节,就完成了。这就是GCC所做的。

两者都是有效的。 C++ 并没有规定编译器必须检查你传递给它的字符串的有效性。
但在这两种情况下,请注意u8前缀与您的问题无关。这只是告诉编译器从“读取字符串时的任何编码转换为UTF-8”。但即使在此转换之前,字符串已经乱码,因为字节对应于ISO-8859字符数据,但编译器认为它们是UTF-8(因为您没有告诉它)。
你看到的问题很简单,编译器不知道从源文件中阅读字符串文字时使用哪种编码。
你注意到的另一件事是,一个没有前缀的“传统”字符串字面量将被编码为编译器喜欢的任何编码。引入u8前缀(以及相应的UTF-16和UTF-32前缀)是为了让您指定希望编译器用哪种编码来写入输出。普通的无前缀文字根本不指定编码,而是由编译器决定。

ejk8hzay

ejk8hzay2#

为了说明这一讨论,这里有一些例子。让我们考虑一下代码:

int main() {
  std::cout << "åäö\n";
}

1)用g++ -std=c++11 encoding.cpp编译它将产生一个可执行文件,它会产生:

% ./a.out | od -txC
0000000 c3 a5 c3 a4 c3 b6 0a

换句话说,每个“字素簇”两个字节(根据unicode行话,即在这种情况下,每个字符),加上最后一个换行符(0a)。这是因为我的文件是用utf-8编码的,输入字符集被cpp假定为utf-8,而执行字符集在gcc中默认为utf-8(参见https://gcc.gnu.org/onlinedocs/cpp/Character-sets.html)。很好
2)现在,如果我将我的文件转换为iso-8859-1,并使用相同的命令再次编译,我得到:

% ./a.out | od -txC
0000000 e5 e4 f6 0a

即现在使用ISO-8859-1对这三个字符进行编码。我不确定这里发生了什么,因为这次cpp似乎正确地猜到了文件是iso-8859-1(没有任何提示),在内部将其转换为utf-8(根据上面的链接),但编译器仍然将iso-8859-1字符串存储在二进制文件中。我们可以通过查看二进制文件的.rodata部分来检查:

% objdump -s -j .rodata a.out

a.out:     file format elf64-x86-64

Contents of section .rodata:
400870 01000200 00e5e4f6 0a00               ..........

(Note“e5 e4 f6”字节序列)。
这是非常有意义的,因为使用latin-1文字的程序员不希望它们在程序输出中以utf-8字符串的形式出现。
3)现在,如果我保留相同的iso-8859-1编码文件,但使用g++ -std=c++11 -finput-charset=iso-8859-1 encoding.cpp编译,那么我会得到一个输出utf-8数据的二进制文件:

% ./a.out | od -txC
0000000 c3 a5 c3 a4 c3 b6 0a

我觉得这很奇怪:源代码编码没有改变,我明确地告诉gcc它是latin-1,结果我得到utf-8!注意,如果我用g++ -std=c++11 -finput-charset=iso-8859-1 -fexec-charset=iso-8859-1 encoding.cpp显式请求exec-charset,这可以被覆盖:

% ./a.out | od -txC
0000000 e5 e4 f6 0a

我不清楚这两个选项是如何相互作用的。
4)现在让我们将“u8”前缀添加到混合中:

int main() {
  std::cout << u8"åäö\n";
}

如果文件是utf-8编码的,那么毫无疑问,使用默认的字符集(g++ -std=c++11 encoding.cpp)进行编译,输出也是utf-8。如果我请求编译器在内部使用iso-8859-1(g++ -std=c++11 -fexec-charset=iso-8859-1 encoding.cpp),输出仍然是utf-8:

% ./a.out | od -txC
0000000 c3 a5 c3 a4 c3 b6 0a

因此,看起来前缀“u8”阻止了编译器将文字转换为执行字符集。更好的是,如果我将相同的源文件转换为iso-8859-1,并使用g++ -std=c++11 -finput-charset=iso-8859-1 -fexec-charset=iso-8859-1 encoding.cpp编译,那么我仍然会得到utf-8输出:

% ./a.out | od -txC
0000000 c3 a5 c3 a4 c3 b6 0a

所以看起来“u8”实际上充当了一个“操作符”,告诉编译器“将此文字转换为utf-8”。

vm0i2vca

vm0i2vca3#

我通过反复试验发现,在MSVC上,"ü""\u00FC"不会产生相同的字符串(ü的代码点为U+00 FC)。
我想有两种可靠的方法可以将UTF-8放在字符串中:使用UTF-8编码单位如下:"\xC3\xBC"u8"\u00FC"。在第一个例子中,你告诉编译器做什么,而在第二个例子中,你想要什么。
使用u8"s\u00FCchtig"(süchtig)而不是"s\xC3\xBCchtig"至少有两个很好的理由:

  • 您可以在任何合理的字符Map中搜索U+00 FC。
  • \u正好需要4个十六进制数字,对于非BMP字符,\U有8个十六进制数字,但是\x会消耗尽可能多的十六进制数字,例如。"s\xC3\xBCchtig"实际上不起作用:它将\xBCc视为1值,这意味着您必须将字符串拆分为两个字面值:"s\xC3\xBC""chtig"

相关问题