【问题标题】:Printing bytes of UTF-8 string in C在 C 中打印 UTF-8 字符串的字节
【发布时间】:2019-04-09 04:34:33
【问题描述】:

我想打印单词“česnek”的单个字节,期望打印 7 个字节,因为“č”被编码为 2 个字节,但它会在终端中打印诸如问号之类的垃圾字符。如果我打印出整数值,我会得到这个序列。

-60 -115 101 115 110 101 107

为什么前两个数字是负数? 这是我用来尝试的代码。

 char *utfstring = "česnek";
 for(size_t i = 0; i < strlen(utfstring); i++) {
 printf("%c ", utfstring[i]);
 }
 for(size_t i = 0; i < strlen(utfstring); i++) {
 printf("%d ", utfstring[i]);
 }

我希望前两个值是 c4 8d 因为 č 是根据 https://www.utf8-chartable.de/unicode-utf8-table.pl?start=256&unicodeinhtml=dec 编码的

【问题讨论】:

  • 在某些系统上char 是signed,在其他系统上是unsigned
  • 写。 “负数”:尝试“%u”作为无符号十进制数。
  • 必须在系统上安装此字符的正确字体,并且给定的终端程序可以访问。在我的系统上,使用printf("%s\n",utfstring); 在"%c " 上循环将不 工作(注入空间中断 utf-8 序列)。编译器不关心这个。 printf 和 %s 也没有,因为它只看到一串字节。只有终端程序/字体会关心。最好使用%2.2X 打印单个字符以单独查看[如有疑问,请针对0xFF 屏蔽该值以避免char 的符号扩展]。

标签: c encoding utf-8


【解决方案1】:

使用(unsigned char)utfstring[i]或0xFF &amp; utfstring[i]得到十六进制输出如下:

char *utfstring = u8"česnek";
for(size_t i = 0; i < strlen(utfstring); i++)
    printf("%02X ", 0xFF & utfstring[i]);

输出:

"C4 8D 65 73 6E 65 6B"

第一个字母字符 č 在 UTF8 中不能用单个字节表示。如果您一次打印一个字节 utfstring,则 UTF8 编码被破坏。

必须打印为u8"č" 或u8"\xC4\x8D"

一般来说,如果您希望将字节序列分解为单独的 Unicode 代码点,您将需要一个 Unicode 库,例如 iconv。如果您只是想查找č,请使用标准字符串函数,例如strstr(utfstring, u8"č")。

【讨论】:

  • “如果你一次打印一个字节的 utfstring,那么 UTF8 编码就会被破坏。”:嗯,字节一次进入输出流。损坏只会来自格式化函数或转换它们的输出流(对于 %c,我认为不会导致 UTF-8 损坏),
  • @TomBlodget 是的 printf("%c%c\n", 0xC4, 0x8D) 可以工作,但 printf("%c %c\n", 0xC4, 0x8D)(中间有空格)会打印一些 ASCII 字符,后者在问题中被尝试。
【解决方案2】:

首先,char 的签名是implementation-defined。最重要的是,您告诉printf() 使用%d 打印一个签名号码。要将它们可移植地打印为无符号数字,您需要将它们转换为 unsigned 并使用 %u format specifier 打印它们:

printf("%u ", (unsigned char) utfstring[i]);

这将处理负数,但您还有另一个问题:C 标准不要求编译器在源代码中接受 UTF-8 编码的字符。标准只保证small set of basic characters。您可能需要查看特定编译器和标准库的文档以了解如何处理。您可能会得到 UTF-8、其他编码或垃圾;无论你得到什么,它都不是便携式的。如果这听起来很蹩脚,你是对的,它是 - C/C++ 在 i18n 方面已经追赶了很长时间。

好消息是,情况正在好转。如果您的编译器支持 C11,您可以并且应该利用 UTF-8 string literals 在字符串中可移植地编码 UTF-8 代码点。

【讨论】:

  • 哦,我明白了,我以为我会先将这些值“c4 8d”,因为根据utf8-chartable.de/…,这是 č 的值
  • (unsigned) utfstring[i] 不会根据需要便携式打印它们。 (unsigned char) utfstring[i] 更有意义,除非你想 -1 作为 FFFFFFFF
  • @chux 我明白你的意思,但%u 不期望unsigned 类型的参数,而不是unsigned char - 所以在这种情况下我们需要双重转换(unsigned) (unsigned char) utfstring[i]?
  • (unsigned char) utfstring[i] 就足够了。对于 ...(省略号)函数的参数,正 int 值必须按照 C11 §6.5.2.2 6 编码与 unsigned 相同。因此,将 "%u" 与 (unsigned char) utfstring[i] 一起使用是很好的指定功能。,
  • @chux 已编辑,谢谢。你碰巧知道这是否也是 C11 之前的情况?
【解决方案3】:

当 UTF 表示为 多字节时,您的 for-loop 逐字节遍历字符值。

char *utfstring = "česnek"; 超过六个字节长! 因为该字符串中的第一个“字符”占用了超过一个字节。 (UTF 表示的巧妙之处在于,每个字节都是自编码的,通过单独检查每个字节的二进制内容,您可以可靠地确定它是什么“类型”字节,以及它落在哪里[如果适用] 在多字节序列中。)

您的逻辑尝试对这些字节使用%c 和%d 格式,但可以说,这两种格式都不是最合适的。 “在这个[人类]环境中,这些不是真正的字符,也不是整数。”试试%x ...十六进制。 “给我看看。”

【讨论】:

  • 哦,我明白了,我以为我首先要这些值“c4 8d”,因为根据utf8-chartable.de/…,这是 č 的值
  • OP 表示他们期望 7 个字节并且他们似乎理解 UTF-8。问题是为什么打印的数字是负小数而不是十六进制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-21
  • 2013-03-09
  • 2011-01-08
  • 1970-01-01
  • 2015-09-01
  • 1970-01-01
相关资源
最近更新 更多