vsnprintf返回超过给定缓冲区大小的大小

3

非常长的字符串,带有myPrint()函数,将会崩溃。

我认为从Linux手册页中可以看出vsnprintf()无法返回写入的大小超过缓冲区长度。

我的预期字符串是缓冲区大小的截断字符串,但是下面的测试代码完全错误。

下面的代码有什么问题吗?

void myPrint(const char* fmt, ...)
{
    char buffer[512] = {0,};

    va_list arg;
    va_start(arg, fmt);

    int r = vsnprintf(buffer, 511, fmt, arg); // buffer size is given
    if (r > 0)              // works correctly
        buffer[r+1] = '\0'; // crash because r is 200,000
    va_end(arg);
}

int main(int, char**)
{
    const char * data = "abcdefg...." // assuming that a length is 200,000 byte string
    myPrint("%s\n", data);
}

你不需要在调用之后终止字符串,vsnprintf 已经做了这个。 - user694733
2个回答

7
不,vsnprintf函数非常明确地返回完整字符串所需的字符数。C11 7.21.6.12p3

vsnprintf函数返回如果n足够大,则将被写入的字符数(不包括结尾的空字符),或者如果出现编码错误则返回负值。因此,只有当返回值为非负且小于n时,才已经完全写入了以空字符结尾的输出。

此外,输入大小应为完整的缓冲区大小,例如这里是512。然后vsnprintf会写入最多511个字符,并在最后一个字符后添加终止符'\0'(C11 snprintf description)
否则,超出第n-1个字符的输出字符将被丢弃而不是写入数组,并在实际写入数组的字符末尾写入空字符。如果在重叠对象之间进行复制,则行为未定义。
此外,请注意(7.21.6.5p2):
[...] 因此,仅当返回值为非负且小于n时,才已完全写入以空字符结尾的输出。
也就是说,如果您的缓冲区是一个512个char的数组,并且您传递了512,则当* snprintf的返回n值为0 <= n <= 511时,字符串已正确编写且未被截断。

需要注意的是,Microsoft Visual C++ 中有一个名为 _vsnprintf 的函数非常不可靠:

[...] 如果要写入的字符数小于或等于计数,则返回已写入的字符数; 如果要写入的字符数大于计数,则这些函数返回-1,表示输出已被截断。


最后,如果你只编写 Linux/Glibc 特定的代码,你也可以考虑使用 vasprintf,它会动态分配足够大的缓冲区来存储整个字符串。

请注意,在 C 语言广泛标准化之前,还有许多旧的 *snprintf() 函数实现也会在输出截断时返回 -1。这是存在的两种主要行为变体之一。 - Andrew Henle
@AndrewHenle 是的,snprintf 是 C99。 - Antti Haapala -- Слава Україні

3

这里有多个需要修复的问题:

  1. You can give complete buffer size to vsnprintf:

    int r = vsnprintf(buffer, sizeof buffer, fmt, arg);
    
  2. You don't need to nul terminate buffer after call. vsnprintf truncates too long string correctly.

  3. vsnprintf returns the length that would have been, if truncation hadn't happened. If you need to detect truncation, you can do it as follows:

    if(r >= sizeof buffer) {
         // Buffer was too small    
    }
    

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