【问题标题】:C99 Standard - fprintf - s conversion with precisionC99 标准 - fprintf - s 精确转换
【发布时间】:2017-02-02 20:22:20
【问题描述】:

假设只有C99 Standard 纸和printf 库函数需要根据此标准实现以使用UTF-16 编码,您能否阐明s 指定精度转换的预期行为?

s 转换的 C99 标准 (7.19.6.1) 说:

如果不存在 l 长度修饰符,则参数应为指向字符类型数组的初始元素的指针。数组中的字符被写入(但不包括)终止空字符。如果指定了精度,则写入的字节数不会超过这个数。如果未指定精度或大于数组的大小,则数组应包含空字符。

如果存在 l 长度修饰符,则参数应为指向 wchar_t 类型数组的初始元素的指针。数组中的宽字符被转换为多字节字符(每个都好像通过调用 wcrtomb 函数,在转换第一个宽字符之前由 mbstate_t 对象描述的转换状态初始化为零)直到并包括终止的空宽特点。生成的多字节字符最多写入(但不包括)终止空字符(字节)。如果未指定精度,则数组应包含空宽字符。如果指定了精度,则写入的字节数不超过(包括移位序列,如果有的话),并且如果要等于由精度给出的多字节字符序列长度,则数组应包含一个空宽字符,该函数需要访问数组末尾的一个宽字符。在任何情况下都不会写入部分多字节字符。

总的来说,我不太理解这一段,特别是“如果指定了精度,则写入的字节数不超过”。

我们以 UTF-16 字符串“TEST”为例(字节序列:0x54、0x00、0x45、0x00、0x53、0x00、0x54、0x00)。

在以下情况下预计会写入输出缓冲区的内容:

  • 如果 精度 为 3
  • 如果 precision 为 9(比字符串长度多一个字节)
  • 如果 precision 为 12(比字符串长度多几个字节)

还有“数组中的宽字符转换为多字节字符”。这是否意味着应该先将 UTF-16 转换为 UTF-8?如果我希望只使用 UTF-16,这很奇怪。

【问题讨论】:

  • %s 接受一个字符串。由于所有空字节,您不能将 UTF-16 存储在 C 字符串中。 (另外,UTF-16 不是字符集;它是一种编码。)
  • "\x54\x00\x45\x00\x53\x00\x54\x00"是一个长度为1的字符串,包含T
  • 你是专门问%ls/wchar_t吗?
  • CHAR_BIT 在您的实现中的值是多少?如果CHAR_BIT == 8,则不能用%s处理UTF-16;您将使用 %ls 并传递 wchar_t * 作为相应的参数。然后,您必须阅读规范的第二段。如果CHAR_BIT == 16,则数据中的八位字节数不能为奇数。然后,您需要了解 wchar_tchar 的关系(它们的大小是否相同?它们是否具有相同的符号?)并解释这两个段落以产生统一的效果 - 除非您决定使用 wchar_t表示 UTF-32。
  • @melpomene 当然可以,您只需要 16 位字符。

标签: c unicode c99


【解决方案1】:

将评论转换为稍微扩展的答案。

CHAR_BIT 在您的实现中的价值是什么?

  • 如果CHAR_BIT == 8,则不能用%s处理UTF-16;您将使用 %ls 并传递 wchar_t * 作为相应的参数。然后你必须阅读规范的第二段。

  • 如果CHAR_BIT == 16,则数据中的八位字节数不能为奇数。然后,您需要了解 wchar_tchar 的关系(它们的大小是否相同?它们是否具有相同的符号?)并解释这两个段落以产生统一的效果 - 除非您决定使用 wchar_t表示 UTF-32。

关键点是,如果CHAR_BIT == 8,UTF-16 不能作为 C 字符串处理,因为有太多有用的字符被编码为一个字节保持零,但那些零字节标志着一个以 null 结尾的结尾细绳。要处理 UTF-16,普通的 char 类型必须是 16 位(或更大)类型(因此 CHAR_BIT > 8),或者您必须使用 wchar_t(和 sizeof(wchar_t) > sizeof(char))。

请注意,规范要求将宽字符转换为合适的多字节表示形式。

如果你想本地输出宽字符,你必须使用 fwprintf()<wchar.h> 中的相关函数,首先在 C99 中定义。那里的规范与fprintf() 的规范有很多共同之处,但也有(不足为奇的)重要区别。

7.29.2.1 fwprintf 函数

s
如果不存在l 长度修饰符,则参数应为指向初始值的指针 包含多字节字符序列的字符数组的元素 从初始移位状态开始。数组中的字符被转换为 如果通过重复调用mbrtowc 函数,转换状态 由在第一个之前初始化为零的 mbstate_t 对象描述 多字节字符被转换,并写入(但不包括) 终止空宽字符。如果指定精度,不超过 写了很多宽字符。如果未指定精度或 大于转换后数组的大小,则转换后的数组应包含 空宽字符。

如果存在l 长度修饰符,则参数应为指向初始值的指针 wchar_t 类型的数组的元素。数组中的宽字符是 最多写入(但不包括)终止空宽字符。如果 指定精度,不超过写入的宽字符数。如果 精度未指定或大于数组大小,数组 应包含一个空宽字符。

【讨论】:

    【解决方案2】:

    wchar_t 不适合用于 UTF-16,仅用于实现定义的固定宽度编码,具体取决于当前语言环境。使用宽字符 API 支持可变长度编码根本没有理智的方法。同样,printfwcrtomb 等函数使用的多字节表示是实现定义的。如果您想使用 Unicode 编写可移植代码,则不能依赖宽字符 API。使用库或编写您自己的代码。

    回答您的问题:带有l 修饰符的fprintf 接受由当前语言环境指定的实现定义的编码中的宽字符串。如果wchar_t 是 16 位,那么这种编码可能是 UTF-16 的混蛋,但正如我上面提到的,没有办法正确支持 UTF-16 代理。然后,此wchar_t 字符串会以实现定义的编码转换为多字节char 字符串。这可能是也可能不是 UTF-8。指定的精度限制了输出字符串中chars 的数量,并增加了不写入部分多字节字符的限制。

    这是一个例子。假设宽字符编码是 UTF-32 和 32 位 wchar_t,而多字节编码是 UTF-8(就像在 Linux 上使用 appropriate locale)。以下代码

    wchar_t w[] = { 0x1F600, 0 }; // U+1F600 GRINNING FACE
    printf("%.3ls", w);
    

    将不打印任何内容,因为生成的 UTF-8 序列有四个字节。仅当您指定的精度至少为四时

    printf("%.4ls", w);
    

    字符将被打印出来。

    编辑:要回答你的第二个问题,不,printf 永远不应该写一个空字符。这句话只表示在某些情况下,需要一个空字符来指定字符串的结尾并避免缓冲区过度读取。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-23
      • 2017-11-09
      • 2011-05-13
      • 1970-01-01
      • 1970-01-01
      • 2011-12-28
      • 1970-01-01
      • 2021-11-02
      相关资源
      最近更新 更多