snprintf()总是终止为null吗?


82

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)在接受的答案中做了说明。:)
Falken教授2014年

@chux我认为这很好,您的评论非常清楚,如果dest i 0长,则不会写入任何内容。我将每条评论作为与stackoverflowers聊天的潜在借口。:)
Falken教授2014年

@教授 法尔肯(Falken)同意那条评论是明确的,但它与答案是多余的-在我的评论中只是错过了。
chux-恢复莫妮卡2014年

stackoverflow.com/a/8712996/193892 Visual Studio现在支持snprintf()
Falken教授

Answers:


71

当其他答案确定时:它应该

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

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


但是,请注意,Microsoft的库没有调用的函数snprintf,而在历史上具有调用的函数_snprintf(请注意下划线),该函数不会附加终止的null。以下是文档(VS 2012,~~ VS 2013):

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

返回值

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

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

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

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

(...)

Visual Studio 2015(VC14)显然引入了符合snprintf函数,但是仍然存在带下划线和空终止行为的遗留函数:

snprintf当len大于或等于count时,该函数会通过在处放置一个空终止符来截断输出 buffer[count-1]。(...)

所有功能的其它snprintf,如果LEN =计数,len个字符被存储在缓冲器中,没有空终止符被附加,(...)


22
微软工程师在引入Aslan时会想到什么,他们_snprintf悄悄地删除了AX的关键安全功能snprintf并允许字符串不以null终止?
Colin D Bennett 2014年

2
@ColinDBennett-这很奇怪而且很烦人,我完全不知道是否有人想到了:-)
Martin Ba

2
@MartinBa是的,很抱歉,我测试的是template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);,我还应该提到只有在使用/ GS(安全检查)编译标志时才会发生这种情况。该函数知道大小,数量和长度。
sekmet64 2014年

3
请注意,除非另外指定,否则mingw64使用(使用?)将Microsoft _snprintf实现用作“常规” snprintf nvd.nist.gov/vuln/detail/CVE-2018-1000101
domenukk

2
@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
科林d贝内特

19

根据snprintf(3)联机帮助页。

的功能snprintf()vsnprintf()至多写入size字节(包括结尾空字节(“\ 0”))至str

因此,是的,如果size> = 1,则无需终止。


3
并为此感谢上帝;这是唯一明智的设计。这些函数的已检查版本的全部内容都是安全的,如果您必须手动执行所有终止恶意代码,那将是很糟糕的。
Kerrek SB 2011年

1
我建议您在依赖此平台之前在您使用的平台上对其进行测试。即使它应该写入空字节,我也知道我遇到了没有的实现(可能是MinGW使用了较旧的MS运行时)。
德米特里(Dmitri)

10

根据C标准,除非缓冲器大小为0,vsnprintf()snprintf()零终止其输出。

snprintf()函数应等效于sprintf(),并加上n参数,该参数说明由s引用的缓冲区的大小。如果n为零,则不写任何内容,而s可以为空指针。否则,将丢弃第n-1st之后的输出字节,而不是将其写入数组,并且在实际写入数组的字节的末尾写入一个空字节。

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

此参考页上描述的功能符合ISO C标准。这里描述的要求和ISO C标准之间的任何冲突都是无意的。POSIX.1-2008的该卷符合ISO C标准。

警惕Microsoft版本的vsnprintf()。当缓冲区中没有足够的空间时,它的行为肯定与标准C版本不同(它返回-1,而标准函数返回所需的长度)。尚不完全清楚Microsoft版本null是否在错误情况下终止其输出,而标准C版本却会终止。

另请注意“您是否使用TR 24731安全功能”的答案(请参阅MSDN中的Microsoft版本vsprintf_s())和Mac解决方案,以获取不安全的C标准库函数的安全替代品?


哦,邪恶,没想到。另一方面... :)
Falken教授

啊,我觉得MS vsprintf中()咬我,我拾起- 1次的习惯
法尔肯教授

4

SunOS的某些较旧版本使用snprintf做一些奇怪的事情,并且可能没有以NUL终止输出,并且返回值与其他所有人正在执行的操作不匹配,但是过去10年中发布的所有内容一直在执行C99的操作说。


我注意到XP是在10多年前发布的。:-)
Falken教授,

今年已经过时了。:)
Falken教授2014年

4

模糊性始于C标准本身。C99和C11的snprintf功能描述相同。这是C99的描述:

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

一方面句子

否则,将丢弃st以外的输出字符,n-1而不是将其写入数组,并且在实际写入数组的字符的末尾写入一个空字符

表示
如果(s指向一个3个字符长的数组的点,和)n为3,则将写入2个字符,并且丢弃第二个字符之外的字符;然后将空字符写在这2个字符之后(空字符将是写入的第三个字符)

我相信这可以回答原始问题。
解答:
如果在重叠的对象之间进行复制,则行为是不确定的。
如果n为0,则不向输出写入任何内容,
否则,如果未遇到编码错误,则输出始终为空终止无论输出是否适合输出数组;否则,将丢弃某些字符,以使输出数组永远不会溢出),
否则(如果遇到编码错误)输出可以保持为非null终止

另一方面
,最后一句话

因此,当且仅当返回值是非负且小于 n

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


我认为以上解释1与答案相矛盾,引起误解和冗长的讨论。这就是为什么描述snprintf函数的最后一句话需要更改以消除任何歧义的原因(这为编写C语言标准的建议书提供了基础)。
我相信可以使用http://en.cppreference.com/w/c/io/fprintf(请参阅参考资料4))中的模棱两可的示例,感谢@“ Martin Ba”的链接。

另请参见问题“ snprintf:是否有任何C标准提案/计划来更改此功能的描述? ”。


4
您的解释1在我看来似乎并不合理。我将该句子解析为“如果...,则输出(顺便说一句,以零结尾)已经完全写好了”,我只能将其理解为#2。
zwol

1
否定的输出未完全写入语句的否定为“完成的终止输出完全写入”。而已。否定的语句本身并不意味着已经写了任何东西(这包括不完整的以空值结尾的输出,不完整的不以空值结尾的输出或无色的绿色概念)。标准中的其他地方指出输出不完整时确切写的内容,该地方指出输出为null终止,除非它为空(n == 0)。
n。代词
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.