【问题标题】:Is snprintf() ALWAYS null terminating?snprintf() 总是空终止吗?
【发布时间】:2011-10-09 22:18:54
【问题描述】:

snprintf 是否总是 null 终止目标缓冲区?

换句话说,这是否足够:

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

如果 somestr 足够长,您是否必须这样做?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

我对标准所说的内容以及某些流行的 libc 可能会做的非标准行为都很感兴趣。

【问题讨论】:

  • 你的意思是在第二个例子中 nul 终止 somestr 或 dst 吗?
  • @chux,Martin Ba 在接受的答案中涵盖了这一点。 :)
  • @chux 我认为这很好,您的评论非常清楚地表明,如果 dest i 0 long,则什么都不会写。我把每一条评论都当作与 stackoverflowers 同伴聊天的潜在借口。 :)
  • @Prof. Falken 同意评论是好的和明确的,但答案是多余的 - 只是在我的评论中错过了。
  • stackoverflow.com/a/8712996/193892 Visual Studio 现在支持 snprintf()

标签: c posix libc


【解决方案1】:

正如其他答案所确立的那样:它should

snprintf ... 将结果写入字符串缓冲区。 (...) 将以空字符终止,除非 buf_size 为零。

因此,您需要注意的是不要将零大小的缓冲区传递给它,因为(显然)它不能将零写入“无处”。


但是,请注意微软的库没有一个名为snprintf的函数,而是历史上只有有一个名为_snprintf(注意前导下划线)的函数,它不附加一个终止的空值。这是文档(VS 2012,~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

返回值

设 len 为格式化数据字符串的长度(不包括 终止空)。 _snprintf 的 len 和 count 以字节为单位,wide _snwprintf 的字符。

  • 如果 len

  • 如果 len = count,则 len 个字符存储在缓冲区中,否 附加空终止符,并返回 len。

  • 如果 len > count,则 count 字符存储在缓冲区中,否 附加空终止符,并返回负值。

(...)

Visual Studio 2015 (VC14) 显然引入了符合标准的 snprintf 函数,但带有前导下划线和 空终止行为的旧函数仍然存在:

snprintf 函数会在 len 更大时截断输出 大于或等于计数,通过在 buffer[count-1]。 (...)

对于snprintf 以外的所有函数其他,如果 len = count, len 字符存储在缓冲区中,没有附加空终止符, (...)

【讨论】:

  • 微软工程师在引入 _snprintf 时以 Aslan 的名义在想什么,它悄悄地删除了 snprintf 的关键安全功能并允许字符串不被空终止?!
  • @ColinDBennett - 这很奇怪而且很烦人,我不知道是否有人想过:-)
  • @MartinBa 是的,对不起,我测试的是template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);,我还应该提到,这只发生在 /GS(安全检查)编译标志。该函数知道大小、计数和长度。
  • 当心 mingw64 使用(使用?)微软 _snprintf 实现作为“正常”snprintf 除非另有说明nvd.nist.gov/vuln/detail/CVE-2018-1000101
  • @Sajjon 这是一个公认的愚蠢(也许是完全原创)的愤怒感叹号(idioms.thefreedictionary.com/in+the+name+of+God),也许有点像碎誓言(en.wikipedia.org/wiki/Minced_oath)。另一个例子可能是“以宙斯的名义……?!” (forum.wordreference.com/threads/in-the-name-of-zeus.2132965)
【解决方案2】:

根据 snprintf(3) 手册页。

函数snprintf()vsnprintf() 最多将size 字节(包括尾随空字节('\0'))写入str

所以,是的,如果 size >= 1,则无需终止。

【讨论】:

  • 感谢上帝;这是唯一明智的设计。这些函数的检查版本的全部意义在于安全,如果你必须手动完成所有终止错误,那就太糟糕了。
  • 我建议在依赖它之前在您正在使用的平台上对其进行测试。即使它应该写入空字节,我知道我遇到了没有的实现(它可能与 MinGW 一起使用,它使用了较旧的 MS 运行时)。
【解决方案3】:

根据 C 标准,除非缓冲区大小为 0,否则 vsnprintf()snprintf() null 将终止其输出。

snprintf() 函数应该等价于sprintf(),并添加了 n 参数,它表示 s 引用的缓冲区的大小。如果 n 为零,则不应写入任何内容,并且 s 可能是空指针。否则,第 n-1 个以外的输出字节将被丢弃而不是写入数组,并且在实际写入数组的字节末尾写入一个空字节。

因此,如果您需要知道要分配多大的缓冲区,请使用大小为零,然后您可以使用空指针作为目标。请注意,我链接到 POSIX 页面,但这些页面明确表示标准 C 和 POSIX 之间没有任何分歧,因为它们涵盖了相同的领域:

此参考页面上描述的功能符合 ISO C 标准。此处描述的要求与 ISO C 标准之间的任何冲突都是无意的。本卷 POSIX.1-2008 遵循 ISO C 标准。

请注意 Microsoft 版本的 vsnprintf()。当缓冲区中没有足够的空间时,它的行为肯定与标准 C 版本不同(它返回 -1,标准函数返回所需的长度)。 Microsoft 版本的 null 会在错误条件下终止其输出,而标准 C 版本会终止输出,这一点并不完全清楚。

另请注意Do you use the TR 24731 safe functions? 的答案(有关vsprintf_s() 的Microsoft 版本,请参阅MSDN)和Mac solution for the safe alternatives to unsafe C standard library functions?

【讨论】:

  • 哦,邪恶的,从没想过。另一方面...... :)
  • 啊,我觉得 MS vsprintf() 咬了我,我捡到了 - 1 个习惯
【解决方案4】:

一些旧版本的 SunOS 使用 snprintf 做了一些奇怪的事情,并且可能没有 NUL 终止输出,并且返回值与其他人所做的不匹配,但过去 10 年发布的任何内容都是照 C99 说的做。

【讨论】:

  • 我注意到 XP 是在 10 多年前发布的。 :-)
  • 今年它已经过时了。 :)
【解决方案5】:

歧义始于 C 标准本身。 C99 和 C11 对 snprintf 函数的描述相同。以下是来自 C99 的描述:

7.19.6.5 snprintf 函数
概要
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
说明
2 snprintf 函数等效于fprintf,除了输出写入数组(由参数s 指定)而不是流。如果n 为零,则不写入任何内容,并且s 可能是空指针。否则,n-1st 之外的输出字符为 被丢弃而不是被写入数组,并且在实际写入数组的字符的末尾写入一个空字符。如果复制发生在重叠的对象之间,则行为未定义。
返回
3 snprintf 函数返回如果n 足够大,将写入的字符数,不包括终止的空字符,如果发生编码错误,则返回负值。因此,当且仅当返回值为非负且小于 n 时,以 null 结尾的输出已被完全写入。

一方面句子

否则,n-1st 之外的输出字符将被丢弃,而不是写入数组,在实际写入的字符末尾写入空字符 进入数组


如果(s 指向一个 3 个字符长的数组,并且)n 是 3,那么 将写入 2 个字符,并且超出第二个被丢弃;然后 空字符写在那些 2 之后(空字符将是第三个字符)

我相信这回答了最初的问题。
答案:
如果复制发生在重叠的对象之间,则行为未定义。
如果 n 为 0,则不会将任何内容写入输出
否则,如果没有遇到编码错误,输出总是以 null 结尾的无论输出是否适合输出数组;如果不适合,则丢弃一些字符这样输出数组永远不会溢出),
否则(如果遇到编码错误)输出可以保持非空终止

另一方面
最后一句

因此,当且仅当返回值为非负且小于n时,以空结尾的输出已被完全写入

给出歧义(或者我的英语不够好)。我至少可以用两种方式解释这句话:
1.输出null-terminated当且仅当返回值为非负且小于n(这意味着如果返回值不是 小于n,即输出(包括终止的空字符)不适合数组,则输出不是空终止的)。
2. 当且仅当返回值非负且小于n时,输出是完整(没有丢弃任何字符)。


我认为上述解释 1 与 THE ANSWER 相矛盾,会引起误解和冗长的讨论。这就是为什么描述 snprintf 函数的最后一句话需要更改以消除任何歧义(这为编写 C 语言标准提案提供了依据)。
我相信可以从http://en.cppreference.com/w/c/io/fprintf(见4))获取非模棱两可的措辞示例,感谢@“Martin Ba”的链接。

另见问题“snprintf: Are there any C Standard Proposals/plans to change the description of this func?”。

【讨论】:

  • 您的解释 1 在我看来根本不可信。我将该句子解析为“输出(顺便说一句,以空终止)已完全编写,如果...”我只能理解为#2。
  • “空终止输出已完全写入”这句话的否定是“空终止输出已完全写入”。而已。否定句本身并不意味着 anything 已被写入(这包括不完整的空终止输出、不完整的非空终止输出或无色的绿色想法)。标准中的其他地方说明了当输出不完整时到底写了什么,并且那个地方声明输出为空终止,除非它是空的(n == 0)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-29
  • 2019-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多