古老的snprintf()
函数...
int snprintf( char *restrict buffer, size_t bufsz, const char *restrict format, ... );
- 返回它打印的字符数,或者更确切地说,如果没有缓冲区大小限制,它将打印的数字。
- 采用缓冲区的大小(以字符/字节为单位)。
缓冲区大小size_t
,但返回类型只是一个int
,这有什么意义?
如果snprintf()
应该能够在缓冲区中打印超过INT_MAX
个字符,那么它肯定必须返回一个ssize_t
或一个size_t
,(size_t) - 1
指示错误,对吧?
如果它不应该能够打印超过INT_MAX
个字符,为什么bufsz
是size_t
而不是unsigned
或int
?或者 - 它是否至少被官方限制为持有不大于INT_MAX
的值?
printf
早于size_t
和类似的"可移植"类型的存在 - 当printf
首次标准化时,sizeof
的结果是int
。
这也是为什么printf
参数列表中读取的格式宽度或*
精度的参数是int
而不是size_t
的原因。
snprintf
是最近的,所以它作为参数的大小被定义为一个size_t
,但返回值被保留为int
,以使其与printf
和sprintf
相同。
请注意,您可以使用这些函数打印INT_MAX
个以上的字符,但如果这样做,则未指定返回值。 在大多数平台上,int
和size_t
都将以相同的方式返回(在主返回值寄存器中),只是size_t
值可能超出了int
的范围。 如此多的平台实际上从所有这些例程中返回一个size_t
(或ssize_t
),超出范围的事情通常可以正常工作,即使标准不需要它。
大小和返回之间的差异已在线程 https://www.austingroupbugs.net/view.php?id=761 的标准组中讨论过。以下是该线程末尾发布的结论:
进一步的研究表明,返回值溢出 int 时的行为由 Wg14 在 C99 中通过将其添加到附录 J 中的未定义行为列表中来阐明。它在C11中更新为以下文本:
"J.2 未定义的行为 在以下情况下未定义该行为: [跳过] — 格式化输出函数传输的字符数或宽字符数(或写入数组,或本应写入数组的字符数)大于 INT_MAX (7.21.6.1、7.29.2.1)。
请注意,此描述没有提到 snprintf 的大小参数或缓冲区的大小。
缓冲区大小
size_t
,但返回类型只是一个整数有什么意义?
官方的C99理由文件没有讨论这些特定的考虑因素,但大概是出于一致性和(单独的)意识形态原因:
-
所有
printf
系列函数都返回具有基本相同意义的int
。 这早在size_t
发明之前就已经定义(针对原始printf
、fprintf
和sprintf
)。 -
从某种意义上说,类型
size_t
是传达大小和长度的正确类型,因此当在 C99 中引入这些参数(连同size_t
本身)时,它被用于snprintf
和vsnprintf
的第二个参数。
如果
snprintf()
应该能够在缓冲区中打印超过INT_MAX
个字符,那么它肯定必须返回一个ssize_t
或一个size_t
,(size_t) - 1
指示错误,对吧?
这将是一个内部更一致的设计选择,但没有。 似乎选择了整个函数系列的一致性。 请注意,此系列中的任何函数都没有记录它们可以输出的字符数限制,并且它们的一般规范意味着没有固有的限制。因此,它们都面临着输出很长的相同问题。
如果它不应该能够打印超过
INT_MAX
个字符,为什么 bufsz 是size_t
而不是unsigned
或int
?或者 - 它是否至少被官方限制为持有不大于INT_MAX
的值?
对第二个参数的值没有记录的约束,除了隐含的约束,即它必须可表示为size_t
。 即使在最新版本的标准中也是如此。 但请注意,也没有任何内容表明类型int
不能表示size_t
可以表示的所有值(尽管实际上在大多数实现中它不能)。
所以是的,当通过这些函数输出非常大的数据时,实现将难以按照规范运行,其中"非常大"依赖于实现。 因此,作为一个实际问题,不应该依赖使用它们在单个调用中发出非常大的输出(除非打算忽略返回值)。
如果
snprintf()
应该能够在缓冲区中打印超过INT_MAX
个字符,那么它肯定必须返回一个ssize_t
或一个size_t
,(size_t) - 1
指示错误,对吧?
差一点。
C 对fprintf()
和朋友也有环境限制。
任何一次转换可以产生的字符数应至少为 4095。 C17dr § 7.21.6.1 15
任何超过4095%
都有可移植性的风险,因此int
,即使是16位(INT_MAX = 32767
),也足以满足大多数可移植代码的目的。
注意:ssize_t
不是 C 规范的一部分。