在理解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等...
我现在比以前更困惑了。。
3条答案
按热度按时间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*,则编译器可能会做两件事(取决于实现:
u8
前缀意味着我应该将这个字符串写成UTF-8。酷,那就不需要转换了。我只写这3个字节,就完成了。这就是GCC所做的。两者都是有效的。 C++ 并没有规定编译器必须检查你传递给它的字符串的有效性。
但在这两种情况下,请注意
u8
前缀与您的问题无关。这只是告诉编译器从“读取字符串时的任何编码转换为UTF-8”。但即使在此转换之前,字符串已经乱码,因为字节对应于ISO-8859字符数据,但编译器认为它们是UTF-8(因为您没有告诉它)。你看到的问题很简单,编译器不知道从源文件中阅读字符串文字时使用哪种编码。
你注意到的另一件事是,一个没有前缀的“传统”字符串字面量将被编码为编译器喜欢的任何编码。引入
u8
前缀(以及相应的UTF-16和UTF-32前缀)是为了让您指定希望编译器用哪种编码来写入输出。普通的无前缀文字根本不指定编码,而是由编译器决定。ejk8hzay2#
为了说明这一讨论,这里有一些例子。让我们考虑一下代码:
1)用
g++ -std=c++11 encoding.cpp
编译它将产生一个可执行文件,它会产生:换句话说,每个“字素簇”两个字节(根据unicode行话,即在这种情况下,每个字符),加上最后一个换行符(0a)。这是因为我的文件是用utf-8编码的,输入字符集被cpp假定为utf-8,而执行字符集在gcc中默认为utf-8(参见https://gcc.gnu.org/onlinedocs/cpp/Character-sets.html)。很好
2)现在,如果我将我的文件转换为iso-8859-1,并使用相同的命令再次编译,我得到:
即现在使用ISO-8859-1对这三个字符进行编码。我不确定这里发生了什么,因为这次cpp似乎正确地猜到了文件是iso-8859-1(没有任何提示),在内部将其转换为utf-8(根据上面的链接),但编译器仍然将iso-8859-1字符串存储在二进制文件中。我们可以通过查看二进制文件的.rodata部分来检查:
(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数据的二进制文件:我觉得这很奇怪:源代码编码没有改变,我明确地告诉gcc它是latin-1,结果我得到utf-8!注意,如果我用
g++ -std=c++11 -finput-charset=iso-8859-1 -fexec-charset=iso-8859-1 encoding.cpp
显式请求exec-charset,这可以被覆盖:我不清楚这两个选项是如何相互作用的。
4)现在让我们将“u8”前缀添加到混合中:
如果文件是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:因此,看起来前缀“u8”阻止了编译器将文字转换为执行字符集。更好的是,如果我将相同的源文件转换为iso-8859-1,并使用
g++ -std=c++11 -finput-charset=iso-8859-1 -fexec-charset=iso-8859-1 encoding.cpp
编译,那么我仍然会得到utf-8输出:所以看起来“u8”实际上充当了一个“操作符”,告诉编译器“将此文字转换为utf-8”。
vm0i2vca3#
我通过反复试验发现,在MSVC上,
"ü"
和"\u00FC"
不会产生相同的字符串(ü
的代码点为U+00 FC)。我想有两种可靠的方法可以将UTF-8放在字符串中:使用UTF-8编码单位如下:
"\xC3\xBC"
或u8"\u00FC"
。在第一个例子中,你告诉编译器做什么,而在第二个例子中,你想要什么。使用
u8"s\u00FCchtig"
(süchtig)而不是"s\xC3\xBCchtig"
至少有两个很好的理由:\u
正好需要4个十六进制数字,对于非BMP字符,\U
有8个十六进制数字,但是\x
会消耗尽可能多的十六进制数字,例如。"s\xC3\xBCchtig"
实际上不起作用:它将\xBCc
视为1值,这意味着您必须将字符串拆分为两个字面值:"s\xC3\xBC""chtig"
。