【问题标题】:Workaround for glibc's printf truncation bug in multi-byte locales?glibc 在多字节语言环境中的 printf 截断错误的解决方法?
【发布时间】:2014-10-11 22:58:34
【问题描述】:

某些基于 GNU 的操作系统发行版 (Debian) 仍然受到 GNU libc 中的一个错误的影响,该错误会导致 printf 系列函数在指定的精度级别截断多字节字符时返回虚假的 -1 .此错误已在 2.17 中修复并向后移植到 2.16。 Debian has an archived bug 对此,但维护人员似乎无意将修复程序向后移植到 Wheezy 使用的 2.13。

以下文字引用自https://sourceware.org/bugzilla/show_bug.cgi?id=6530。 (请不要再次编辑块引用内联。)

这里有一个更简单的测试用例,由 Jonathan Nieder 提供:

#include <stdio.h>
#include <locale.h>

int main(void)
{
    int n;

    setlocale(LC_CTYPE, "");
    n = printf("%.11s\n", "Author: \277");
    perror("printf");
    fprintf(stderr, "return value: %d\n", n);
    return 0;
}

在 C 语言环境下会做正确的事:

$ LANG=C ./test
Author: &#65533;
printf: Success
return value: 10

但不在 UTF-8 语言环境下,因为 \277 不是有效的 UTF-8 序列:

$ LANG=en_US.utf8 ./test
printf: Invalid or incomplete multibyte or wide character

值得注意的是,printf 在这种情况下也会用\0 覆盖输出数组的第一个字符。

我目前正在尝试改进 MUD 代码库以支持 UTF-8,不幸的是,代码中充斥着使用任意 sprintf 精度来限制发送到输出缓冲区的文本量的情况。由于大多数程序员在这种情况下不期望返回-1,因此这个问题变得更糟,这可能导致未初始化的内存读取和由此产生的不良后果。 (已经在 valgrind 中捕获了一些案例)

有没有人为他们的代码中的这个错误想出一个简洁的解决方法,不涉及重写具有任意长度精度的格式化字符串的每一次调用?我可以将截断的 UTF-8 字符写入我的输出缓冲区,因为在套接字写入之前在我的输出处理中清理它是相当简单的,而且在最终会解决的问题上投入这么多精力似乎有点过头了再过几年。

【问题讨论】:

  • 据我所知,如果字符被截断,则根本不会输出。只有在尝试输出无效字符时才会得到 -1。
  • 有趣。在 glibc 2.18 上,这种行为不存在。 printf 似乎将其视为字节字符串,就像它在 C 语言中一样。
  • @ZanLynx 抱歉,链接中指定了版本号,但我应该在我的帖子中提到它们。它已在 2.17 中修复并向后移植到 2.16,但显然 Debian 有 no intention 将修复程序向后移植到 Wheezy 的 2.13。
  • 我很好奇您的 MUD 代码是否有任何理由实际处理 UTF-8,或者只是传递它就足够了?如果是这样,只需将您的语言环境强制为 C 并将所有文本处理为 8 位干净的字节缓冲区。
  • @ZanLynx:如果您出于任何原因有最大长度,那将不起作用。要截断,您需要解释。

标签: c utf-8 printf glibc


【解决方案1】:

我猜,而且 cmets 似乎已经证实了这个问题,即您没有使用 C 库的所有特定于语言环境的功能。在这种情况下,您最好不要将语言环境更改为基于 UTF-8 的语言环境,并将其保留在您的代码假定的单字节语言环境中。

当您确实需要将 UTF-8 字符串作为 UTF-8 字符串处理时,您可以使用专门的代码。编写自己的 UTF-8 处理例程并不难。您甚至可以下载Unicode Character Database 并进行一些相当复杂的字符分类。如果您更喜欢使用第三方库来处理 UTF-8 字符串,则可以使用您在 cmets 中提到的 ICU。这是一个相当重量级的库,之前的问题推荐了几个lighter weight alternatives

还可以根据需要来回切换 C 语言环境,以便您可以使用 C 库的功能。但是,您需要检查这对性能的影响,因为切换语言环境可能是一项昂贵的操作。

【讨论】:

  • 我宁愿引入一个库也不愿重新发明轮子,特别是因为我可以访问标准库未公开的 Unicode 字符属性。我最初的目标是避免添加依赖项,但是当 glibc 在主要软件发行版中被破坏时,这是一厢情愿的想法。我同意 ICU 有点矫枉过正,目前正在评估替代方案。
  • 将 UCD 表转换为 C 表并不难,您可以使用自己的代码访问 Unicode 字符属性。
  • 话虽如此,但要编写的代码总是比您意识到的要多。一个人真的需要编写自己的实现方法吗?将输入字符串标准化为 NFC 格式,例如?我最终选择了GNU libunistring;它提供了简单性、易于理解的文档和一组sprintf 系列映射之间的良好平衡,以适应现有的基于列的格式代码。
  • 当然,不知道你需要做什么 UTF-8 处理,我只是指出这些事情在你自己的代码中处理 UTF-8 并不难。 GNU libunistring 听起来很适合您的项目,但是当您找不到合适的现有库时,有时值得自己动手而不是尝试使某些东西起作用。我所知道的是,编写自己的代码比试图让 GNU libc 做你想做的事情要好。
猜你喜欢
  • 2012-07-11
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
  • 2018-08-15
  • 1970-01-01
  • 2019-12-16
  • 2014-04-21
  • 2016-01-29
相关资源
最近更新 更多