u8文字应该如何运作?

时间:2014-05-05 12:01:24

标签: c++ c++11

无法理解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等等......

我现在比以前更加困惑......

2 个答案:

答案 0 :(得分:14)

u8前缀实际上只是意味着“在编译此代码时,从此文字生成UTF-8字符串”。它没有说明编译器应该如何解释源文件中的文字。

所以你有几个因素在起作用:

  1. 哪个编码是写入的源文件(在您的情况下,显然是ISO-8859)。根据这种编码,字符串文字是“åäö”(3个字节,包含值0xc5,0xe4,0xf6)
  2. 在读取源文件时,编译器假定的编码是什么? (我怀疑GCC默认为UTF-8,但我可能错了。
  3. 编译器用于对象文件中生成的字符串的编码。您可以通过u8前缀将其指定为UTF-8。
  4. 最有可能的是,#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转义序列({{1而不是\u00E5,例如)

    编辑:

    为了澄清一点,当您在源代码中指定带有å前缀的字符串文字时,您告诉编译器“无论您在读取时使用了哪种编码源文本,请在将其写入目标文件时将其转换为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前缀 nothing 与您的问题有关。这只是告诉编译器从“读取它时字符串的任何编码转换为UTF-8”。但即使在此转换之前,字符串已经出现乱码,因为字节对应于ISO-8859字符数据,但编译器认为它们是UTF-8(因为你没有告诉它)。

    您遇到的问题很简单,当从源文件中读取字符串文字时,编译器不知道要使用哪种编码。

    您注意到的其他事情是,没有前缀的“传统”字符串文字将使用编译器喜欢的任何编码进行编码。精确地引入了u8前缀(以及相应的UTF-16和UTF-32前缀),以允许您指定编译器要在哪个编码中编写输出。普通前缀为少的文字不指定完全编码,由编译器决定一个。

答案 1 :(得分:2)

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

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(参见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进行编译,那么我得到一个二进制文件,其中包含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编码的,不出所料地使用默认值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”。