swprintf截断会导致意外输出

时间:2019-02-05 23:44:03

标签: c++ printf

我正在修复在linux和Windows上运行的旧代码,在某些情况下,本应包含格式化内容的缓冲区小于该内容。

代码使用swprintf,它根据documentation

  

大小-最多可写1个字符,加上空终止符

确实会截断字符串,但是在coliru上尝试时,我遇到了意外的结果:

#include <iostream> 
#include <string> 
#include <cwchar> 

int main()
{

    wchar_t wide[5];

    std::swprintf(wide, sizeof wide/sizeof *wide, L"%ls", L"111111111");

    std::wcout << wide;
}

将产生1111??,但

#include <iostream> 
#include <string> 
#include <cwchar> 

int main()
{

    wchar_t wide[20];

    std::swprintf(wide, sizeof wide/sizeof *wide, L"%ls", L"111111111");

    std::wcout << wide;
}

工作正常。

怎么了?

P.S。 我希望可以将所有内容更改为C ++流/字符串,但是我不能,wchar_t数组随处可见

1 个答案:

答案 0 :(得分:4)

tl; dr: 出于某种原因,这些空终止语义取决于成功的函数调用,而对于swprintf只有缓冲区足够大时,它才会成功。因此,您第一次尝试的数组不是以空值结尾的。


这很微妙,但是swprintfsnprintf不同。它不会写“最多N-1个字符”,并认为在所有情况下都可以成功。

以下是同一文档中有关swprintf的返回值的说明:

  

返回值:如果成功则写入的宽字符数(不计算终止的空宽字符),如果发生编码错误或要生成的字符数等于或大于,则为负数比大小(包括大小为零时)

实际上是your attempt returns -1

由此(以及引号下的注释),我们可以确定,如果提供的输出缓冲区中没有足够的字节,swprintf会将操作视为 failure 。它不会溢出该缓冲区,但它也可能无法完成其工作,并且其工作包括编写NULL终止符。没有该NULL终止符,您[有效地]传递给wchar_t*的{​​{1}}将超出范围,并且您的程序具有不确定的行为。


我承认,在随便阅读的情况下,这似乎与std::wcout参数周围的语义矛盾,C11为此:

  

最多写入size个宽字符,包括一个终止的空宽字符,该字符总是被添加(除非n为零)。

…没有说明该函数调用是否成功的任何条件。

在标准中可能存在编辑缺陷或实施错误。 但是,即使都不是真的,您的函数调用也被认为是不成功的,我不认为您应该相应地依赖结果。

我们至少可以看到 libc 的意图与上述this manual page on Formatted Output Functions中的破败相符:

  

返回值是为给定输入生成的字符数,不包括结尾的null。如果不是所有输出都适合提供的缓冲区,则返回负值。您应该使用更大的输出字符串再试一次。注意:这与snprintf处理这种情况的方式不同。


您将必须注意上述注意事项:

  

尽管窄字符串提供了std :: snprintf,这使得可以确定所需的输出缓冲区大小,但宽字符串没有等效项,并且为了确定缓冲区大小,程序可能需要调用std :: swprintf,检查结果值,然后重新分配更大的缓冲区,然后重试直到成功。

…或完全切换到其他功能。