C99标准 - fprintf - 带有精度的s转换

3
假设只有 C99标准 这篇论文,需要根据该标准来实现 printf 库函数以便使用 UTF-16 编码。请问,当指定精度时,s 转换的期望行为是什么?
C99 标准(7.19.6.1)中对于 s 转换的说明如下:
如果没有指定 l 长度修饰符,参数应为字符类型数组的初始元素的指针。从数组中写入字符直到(但不包括)终止空字符。如果指定了精度,则最多只写入那么多字节。如果未指定精度或精度大于数组大小,则该数组应包含空字符。
如果存在 l 长度修饰符,则参数应为 wchar_t 类型数组的初始元素的指针。从数组中转换宽字符为多字节字符(每个字符都像调用 wcrtomb 函数一样,使用 mbstate_t 对象描述的转换状态,在第一个宽字符转换之前初始化为零),直到包括终止空宽字符为止。将生成的多字节字符写入到(但不包括)终止空字符(字节)。如果未指定精度,则该数组应包含空宽字符。如果指定了精度,则最多只写入那么多字节(包括移位序列,如果有的话),如果函数需要访问数组末尾后面的宽字符才能等于精度给出的多字节字符序列长度,则该数组应包含空宽字符。在任何情况下都不会写入部分多字节字符。
我不太理解这段话的总体意思,尤其是“如果指定了精度,则最多只能写入那么多字节”的陈述。
例如,我们拿UTF-16字符串“TEST”(字节序列:0x54、0x00、0x45、0x00、0x53、0x00、0x54、0x00)来举例。
在以下情况下,预期将写入输出缓冲区的内容是什么:
- 如果精度为3 - 如果精度为9(比字符串长度多一个字节) - 如果精度为12(比字符串长度多几个字节)
然后还有“从数组中读取的宽字符将转换为多字节字符”。这是否意味着UTF-16应该先转换为UTF-8?如果我只想使用UTF-16,那么这很奇怪。

%s 接受一个字符串。由于所有的空字节,你不能在 C 字符串中存储 UTF-16。(此外,UTF-16 不是一个字符集,而是一种编码方式。) - melpomene
"\x54\x00\x45\x00\x53\x00\x54\x00" 是一个长度为1的字符串,包含字符 T - melpomene
你是在特别询问 %ls / wchar_t 吗? - melpomene
1
你的实现中CHAR_BIT的值是多少?如果CHAR_BIT == 8,那么你无法使用%s处理UTF-16;你需要使用%ls,并将wchar_t *作为相应的参数传递。然后你需要阅读规范的第二段。如果CHAR_BIT == 16,那么数据中不能有奇数个八位字节。接下来,你需要了解wchar_tchar的关系(它们的大小是否相同?是否具有相同的符号性?),并解释两个段落以得出统一的效果 —— 除非你决定让wchar_t表示UTF-32。 - Jonathan Leffler
1
@melpomene 当然可以,你只需要16位字符。 - n. m.
显示剩余5条评论
2个回答

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字符串,因为有太多有用的字符使用一个字节编码为零,但这些零字节标记了空终止字符串的结尾。要处理UTF-16,要么普通的char类型必须是16位(或更大)类型(因此CHAR_BIT > 8),要么您必须使用wchar_t(并且sizeof(wchar_t) > sizeof(char))。
请注意,规范期望将宽字符转换为适当的多字节表示形式。
如果您想本地输出宽字符,则必须使用来自<wchar.h>fwprintf()和相关函数,这在C99中首次定义。该规范与fprintf()的规范有很多相似之处,但有(可以预见的)重要差异。 7.29.2.1 fwprintf函数s
如果没有出现l长度修饰符,则参数应为指向包含以初始转换状态开始的多字节字符序列的字符数组的初始元素的指针。从数组中转换的字符就好像通过重复调用mbrtowc函数进行,其中转换状态由在第一个多字节字符转换之前初始化为零的mbstate_t对象描述,并写入直到(但不包括)终止空宽字符。如果指定了精度,则最多写入那么多个宽字符。如果未指定精度或精度大于转换数组的大小,则转换数组应包含一个空宽字符。
如果出现l长度修饰符,则参数应为wchar_t类型数组的初始元素的指针。从数组中写入宽字符,直到(但不包括)终止空宽字符。如果指定了精度,则最多写入那么多个宽字符。如果未指定精度或精度大于数组的大小,则该数组应包含一个空宽字符。

1

wchar_t不适用于UTF-16,只适用于依赖于当前区域设置的实现定义的固定宽度编码。使用宽字符API支持可变长度编码没有明智的方法。同样,像printfwcrtomb函数使用的多字节表示是实现定义的。如果要编写使用Unicode的可移植代码,则不能依赖于宽字符API。使用库或编写自己的代码。

回答您的问题:fprintfl修饰符一起接受一个使用当前区域设置指定的实现定义编码的宽字符字符串。如果wchar_t为16位,则该编码可能是UTF-16的杂交版本,但正如我上面提到的,没有办法正确支持UTF-16代理项。然后将此wchar_t字符串转换为使用实现定义编码的多字节char字符串。这可能是UTF-8,也可能不是。指定的精度限制输出字符串中char的数量,并添加了不写入部分多字节字符的限制。

这里有一个例子。假设宽字符编码为UTF-32,使用32位,而多字节编码为UTF-8(例如在Linux上使用适当的locale)。以下代码:
wchar_t w[] = { 0x1F600, 0 }; // U+1F600 GRINNING FACE
printf("%.3ls", w);

由于生成的UTF-8序列有四个字节,因此将不会打印任何内容。只有在指定至少四个精度的情况下才会打印。

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

将打印该字符。

编辑:回答您的第二个问题,不应该使用printf写入空字符。该句仅意味着在某些情况下需要空字符来指定字符串的结尾并避免缓冲区读取超限。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接