我正在修复在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
数组随处可见
答案 0 :(得分:4)
tl; dr: 出于某种原因,这些空终止语义取决于成功的函数调用,而对于swprintf
只有缓冲区足够大时,它才会成功。因此,您第一次尝试的数组不是以空值结尾的。
这很微妙,但是swprintf
与snprintf
不同。它不会写“最多N-1个字符”,并认为在所有情况下都可以成功。
以下是同一文档中有关swprintf
的返回值的说明:
返回值:如果成功则写入的宽字符数(不计算终止的空宽字符),如果发生编码错误或要生成的字符数等于或大于,则为负数比大小(包括大小为零时)
由此(以及引号下的注释),我们可以确定,如果提供的输出缓冲区中没有足够的字节,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,检查结果值,然后重新分配更大的缓冲区,然后重试直到成功。
…或完全切换到其他功能。