难以理解u8文字的语义,或者更确切地说,理解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等等......
我现在比以前更加困惑......
该 u8
前缀实际上只是意味着“在编译此代码时,从此文字生成UTF-8字符串”。它没有说明编译器应该如何解释源文件中的文字。
所以你有几个因素在起作用:
- 哪种编码是写入的源文件(在您的情况下,显然是ISO-8859)。根据这种编码,字符串文字是“åäö”(3个字节,包含值0xc5,0xe4,0xf6)
- 哪个编码执行编译器 承担 在阅读源文件时? (我怀疑海湾合作委员会默认使用UTF-8,但我可能错了。
- 编译器用于对象文件中生成的字符串的编码。您可以通过指定为UTF-8
u8
字首。
最有可能的是,#2是出错的地方。如果编译器将源文件解释为ISO-8859,那么它将读取三个字符,将它们转换为UTF-8,然后编写这些字符,给你一个6字节(我认为每个字符编码为UTF中的2个字节) -8)字符串作为结果。
但是,如果它假定源文件是UTF-8,那么它根本不需要进行转换:它读取3个字节,它假定为UTF-8(即使它们是UTF的无效垃圾值) -8),既然你要求输出字符串也是UTF-8,它只输出相同的3个字节。
您可以告诉GCC要采用哪种源编码 -finput-charset
,或者您可以将源编码为UTF-8,或者您可以使用 \uXXXX
字符串文字中的转义序列( \u00E5
代替 å
, 例如)
编辑:
为了澄清一点,当你用。指定字符串文字时 u8
在您的源代码中添加前缀,然后您告诉编译器“无论您在何时使用哪种编码 读 源文本,请在将其写入目标文件时将其转换为UTF-8“。您没有说明如何解释源文本。这取决于编译器决定(可能基于您传递的标志)对它来说,可能是基于进程的环境,或者可能只是使用硬编码的默认值)
如果源文本中的字符串包含字节0xc5,0xe4,0xf6, 和 你告诉它“源文本编码为ISO-8859”,然后编译器会识别出“字符串由字符组成”åäö“。它会看到 u8
前缀,并将这些字符转换为UTF-8,将字节序列0xc3,0xa5,0xc3,0xa4,0xc3,0xb6写入目标文件。在这种情况下,您最终会得到一个有效的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前缀),以允许您指定希望编译器写入输出的编码。普通前缀为少的文字根本不指定编码,留下它由编译器决定一个。
为了说明这个讨论,这里有一些例子。我们考虑一下代码:
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编码的,输入-charset假定是cpp的utf-8,默认情况下exec-charset是utf-8(见gcc) 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 ..........
(注意“e5e4f6”字节序列)。
这是完全合理的,因为使用latin-1文字的程序员不希望它们在程序的输出中作为utf-8字符串出现。
3)现在,如果我保持相同的iso-8859-1编码文件,但编译 g++ -std=c++11 -finput-charset=iso-8859-1 encoding.cpp
,然后我得到一个ouptuts utf-8数据的二进制文件:
% ./a.out | od -txC
0000000 c3 a5 c3 a4 c3 b6 0a
我发现这很奇怪:源编码没有改变,我明确告诉gcc它是latin-1,结果我得到了utf-8!请注意,如果我明确请求exec-charset,则可以覆盖它 g++ -std=c++11 -finput-charset=iso-8859-1 -fexec-charset=iso-8859-1 encoding.cpp
:
% ./a.out | od -txC
0000000 e5 e4 f6 0a
我不清楚这两个选项如何相互作用......
4)现在让我们在混音中添加“u8”前缀:
int main() {
std::cout << u8"åäö\n";
}
如果文件是utf-8编码的,不出所料地使用默认值char-sets进行编译(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”。