【问题标题】:How are u8-literals supposed to work?u8-literals 应该如何工作?
【发布时间】:2014-05-05 12:01:24
【问题描述】:

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

我现在比以前更糊涂了……

【问题讨论】:

  • "源文件是 ISO-8859(-1)" 并且 gcc 应该知道...如何?使用-finput-charset=...或者使用utf8源文件
  • 实际上 gcc 应该查看您的语言环境来确定编码,但我听说这方面存在错误,所以最好还是明确指定输入字符集。
  • @Fredrik 我对文本文字以及它们如何解释/存储以及它们与源文件编码的关系有类似的疑问,但my question was about Raw string literals
  • @Fredrik 但源输入的编码与编译器对其输出的字符串使用的编码无关。没有理由对两者使用相同的编码。源编码 can 在 TU 之间有所不同,但如果结果程序中的字符串文字因此使用不同的编码,那将是非常出乎意料的。 TU 编码不影响u8 文字的结果。对你的编译器撒谎(或不通知它)源文本是什么编码意味着编译器不可能正确转换为 UTF-8 或任何其他编码
  • 如果你想让任何个软件正确处理文本,不管它是编译器、文本编辑器还是其他任何东西,那么你必须确保它知道源文本使用哪种编码,并且您必须告诉它输出使用哪种编码。如果您在它认为看到 UTF-8 文本时给它 ISO-8859 文本,那么无论您告诉它转换为哪种编码,它都会生成垃圾输出。

标签: c++ c++11


【解决方案1】:

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

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

  1. 哪个编码是写入的源文件(在您的情况下,显然是 ISO-8859)。根据这种编码,字符串文字是“åäö”(3 个字节,包含值 0xc5、0xe4、0xf6)
  2. 编译器在读取源文件时采用哪种编码方式? (我怀疑 GCC 默认为 UTF-8,但我可能是错的。
  3. 编译器用于在目标文件中生成的字符串的编码。您可以通过 u8 前缀将其指定为 UTF-8。

最有可能的是,#2 是出错的地方。如果编译器将源文件解释为 ISO-8859,那么它将读取这三个字符,将它们转换为 UTF-8,然后写入它们,给你一个 6 字节(我认为这些字符中的每一个都编码为 UTF 中的 2 个字节-8) 作为结果的字符串。

但是,如果它假定源文件是 UTF-8,那么它根本不需要进行转换:它读取 3 个字节,它假定是 UTF-8(即使它们是无效垃圾UTF-8 的值),并且由于您要求输出字符串也为 UTF-8,因此它只输出相同的 3 个字节。

您可以使用-finput-charset 告诉 GCC 采用哪种源编码,或者您可以将源编码为 UTF-8,或者您可以在字符串文字中使用\uXXXX 转义序列(\u00E5 而不是@987654326 @,例如)

编辑:

澄清一下,当您在源代码中指定带有 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(因为你没有告诉它其他)。

您看到的问题只是编译器在读取源文件中的字符串文字时不知道使用哪种编码。

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

【讨论】:

  • 它读取 3 个字节,假定为 UTF-8(即使它们是 UTF-8 的无效垃圾值)...这是 clang++ ,例如,给你一个错误信息,告诉你字节是无效的。
  • @Massa,我没有在工作中使用 clang++。我是否正确理解你,如果源文件被编码,比如说 ISO8859-1,并且 clang++ 得到这个信息,它会将扩展的 ASCII 转换为相应的 utf8 表示?否则我没有得到用例:)
  • @jalf,如果我明确告诉编译器使用哪种编码,它会按预期工作。正如马萨所说,如果字符不是有效的 utf8,clang++ 会报告错误,对我来说,这是一种首选行为(鉴于 clang++ 能够进行实际转换)。
  • @Fredrik 是的,我同意,这绝对是首选行为。是的,Clang 唯一不同的是它警告你如果你给它提供垃圾 UTF-8。 Clang 和 G++ 都可以执行转换如果他们知道要执行哪个转换。如果您告诉编译器源是 ISO-8859,并要求它生成一个 UTF-8 字符串,那么它将执行必要的转换。问题是你没有告诉它来源是 ISO-8859
  • @Fredrik 正如我在您的问题下的评论中所说,这没有什么意外的。编译器没有义务将 ISO-8859 用于常规文字。
【解决方案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 编码的,输入字符集被 cpp 假定为 utf-8,而 exec-charset 在 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               ..........

(注意“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 明确请求执行字符集,则可以覆盖此设置:

% ./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”。

【讨论】:

    猜你喜欢
    • 2016-02-16
    • 2010-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-31
    • 1970-01-01
    相关资源
    最近更新 更多