swprintf截断导致意外输出

swprintf truncation causes unexpected output

本文关键字:意外 输出 swprintf      更新时间:2023-10-16

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

代码使用swprintf,根据文档

size-最多可以写入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++流/string,但我做不到,wchar_t数组在的所有地方都使用

tl;dr: 由于这样或那样的原因,这些null终止语义取决于函数调用的成功,对于swprintf,只有当缓冲区足够大时,它才会成功。因此,第一次尝试中的数组不是null终止的


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

以下是相同文档对swprintf:返回值的说明

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

实际上,您的尝试返回-1。

由此(以及引用下面的注释),我们可以确定,如果所提供的输出缓冲区中没有足够的字节,swprintf将操作视为失败。它不会溢出缓冲区,但也可能无法完成其工作,其工作包括写入NULL终止符。如果没有NULL终止符,您[有效地]传递给std::wcoutwchar_t*将超出界限,并且您的程序具有未定义的行为。


我承认,随便一读,这似乎与围绕size参数的语义相矛盾,C11表示:

写入不超过n宽的字符,包括一个终止的null宽字符,该字符始终被添加(除非n为零)。

…而不说明函数调用是否成功的任何条件。

可能有将其称为标准中的编辑缺陷或实现错误的余地但是即使两者都不是真的,您的函数调用也被认为是不成功的,我认为您不应该相应地依赖结果。

我们至少可以从格式化输出函数:的手册页面中看到libc意图与上述运行模式相匹配

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


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

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

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