使用snprintf避免缓冲区溢出。

22

我正在使用如下方式来使用snprintf,以避免缓冲区溢出:

char err_msg[32] = {0};
snprintf(err_msg, sizeof(err_msg) - 1, "[ ST_ENGINE_FAILED ]");

我在字符串长度超过32个字节时,加入了-1以保留空间给空终止符。

我的思路正确吗?

平台:

  • GCC 4.4.1
  • C99

2
附注:GCC不支持C99:http://gcc.gnu.org/c99status.html - Bastien Léonard
2
你是否知道,在现代环境中,gcc和标准库组合不包括snprintf函数? - Left For Archive
3
一到两年前,我使用MinGW时,实际上是调用了微软的 _snprintf(),它的行为不像标准的 snprintf()(我认为它不总是以空字符结尾字符串)。 - Bastien Léonard
6个回答

36

正如其他人所说,你在这种情况下不需要-1。如果数组大小固定,我会使用strncpy。它是用于复制字符串的 - sprintf 是用于进行困难格式化的。但是,如果数组的大小未知或者你想确定为格式化字符串需要多少存储空间。这就是我真正喜欢的标准指定版本的snprintf的地方:

char* get_error_message(char const *msg) {
    size_t needed = snprintf(NULL, 0, "%s: %s (%d)", msg, strerror(errno), errno);
    char  *buffer = malloc(needed+1);
    sprintf(buffer, "%s: %s (%d)", msg, strerror(errno), errno);
    return buffer;
}

结合使用va_copy,您可以创建非常安全的格式化字符串操作。


5
如果字符串太长而无法适应目标,请勿使用strncpy()函数;如果复制的字符串太长,则strncpy()不会在其结尾添加空字符。此外,即使源字符串只有1个字节,目标字符串有1兆字节,strncpy()也会始终复制N个字符。 - Jonathan Leffler
2
这段代码有一个错误:“成功完成后,snprintf()函数将返回要写入s的字节数(假设n足够大,不包括终止的空字节)。因此,您必须为终止的空字节分配额外的一个字节。” - Jeremy Salwen
2
@JeremySalwen - 谢谢你发现了这个问题...这就是我写Java的后果 =) - D.Shawley
2
你可以简单地使用sprintf(buffer, …),因为你已经保留了恰好正确大小的buffer。这将避免这个错误。 - Pascal Cuoq
2
@PascalCuoq非常好的观点...不确定我是如何错过这个或者为什么它在四年半内没有被注意到。 - D.Shawley
显示剩余5条评论

14
您不需要使用-1,因为文档中指出:

函数snprintf()和vsnprintf()不会写入超过size个字节的数据(包括结尾处的'\0')。

请注意“包括结尾处的'\0'”这一部分内容。

10

不需要-1。C99的snprintf始终会添加结束符。Size参数指定输出缓冲区的大小,包括零终止符。代码因此变成:

char err_msg[32];
int ret = snprintf(err_msg, sizeof err_msg, "[ ST_ENGINE_FAILED ]");

ret 包含实际打印的字符数(不包括字符串中的零终止符)。

不要与 Microsoft 的 _snprintf(C99 之前)混淆,该函数不会添加空终止符,并且其行为完全不同(例如,如果缓冲区不够大,则返回 -1 而非应打印的长度)。如果使用 _snprintf,则应使用与您问题中相同的代码。


3
根据 snprintf(3) 的说明:

snprintf()vsnprintf() 函数不会写入超过指定的 size 字节数(包括结尾的 '\0')。


0
针对所给的示例,你应该这样做:
char err_msg[32];
strncpy(err_msg, "[ ST_ENGINE_FAILED ]", sizeof(err_msg));
err_msg[sizeof(err_msg) - 1] = '\0';

或者更好的方法:

char err_msg[32] = "[ ST_ENGINE_FAILED ]";

1
如另一个答案的评论所述:如果字符串太大而无法适应目标,请勿使用strncpy();如果复制的内容过长,strncpy()不会在其末尾添加空字符。此外,它始终复制N个字符 - 即使源是1个字节,目标是1兆字节。 - Jonathan Leffler
1
@Jonathan Leffler,您对strncpy()函数复制的字节数的描述是不正确的。“最多复制n个字节的src。”我已经调整了示例以修复空终止。 - Matt Joiner
@anacrolix:你的示例并不保证 NULL 结尾。它确保 strncpy() 永远不会覆盖缓冲区中的最后一个字符。你必须显式地执行 err_msg[sizeof(err_msg)-1] = '\0'; 才能获得终止符。 - D.Shawley

-1

sizeof会返回数据类型在内存中使用的字节数,而不是字符串的长度。例如,在32位系统上,sizeof(int)返回4个字节(取决于具体实现方式)。由于您在数组中使用了一个常量,因此可以将其传递给printf函数。


1
他将 sizeof 应用于目标数组,这是完全正确的。 - caf
32 是正确的。在这种情况下,他不想要字符串的大小(由strlen给出),而是想要“err_msg”缓冲区容量(以确保它不会超过其末尾写入)。 - João Portela

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