swprintf截断导致意外输出
swprintf truncation causes unexpected output
我正在修复在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终止的
这很微妙,但swprintf
与snprintf
不同。它不写"最多N-1个字符",并认为这在所有情况下都是成功的。
以下是相同文档对swprintf
:返回值的说明
返回值:如果成功,写入的宽字符数(不包括终止的空宽字符);如果发生编码错误,或者如果要生成的字符数等于或大于大小(包括大小为零时),则返回负值
实际上,您的尝试返回-1。
由此(以及引用下面的注释),我们可以确定,如果所提供的输出缓冲区中没有足够的字节,swprintf
将操作视为失败。它不会溢出缓冲区,但也可能无法完成其工作,其工作包括写入NULL终止符。如果没有NULL终止符,您[有效地]传递给std::wcout
的wchar_t*
将超出界限,并且您的程序具有未定义的行为。
我承认,随便一读,这似乎与围绕size
参数的语义相矛盾,C11表示:
写入不超过
n
宽的字符,包括一个终止的null宽字符,该字符始终被添加(除非n
为零)。
…而不说明函数调用是否成功的任何条件。
可能有将其称为标准中的编辑缺陷或实现错误的余地但是即使两者都不是真的,您的函数调用也被认为是不成功的,我认为您不应该相应地依赖结果。
我们至少可以从格式化输出函数:的手册页面中看到libc意图与上述运行模式相匹配
返回值是为给定输入生成的字符数,不包括后面的null。如果不是所有输出都适合所提供的缓冲区,则返回负值。您应该使用更大的输出字符串重试。注意:这与snprintf处理这种情况的方式不同。
您必须注意上述注意事项:
虽然窄字符串提供std::snprintf,这可以确定所需的输出缓冲区大小,但宽字符串没有等效的缓冲区大小。为了确定缓冲区的大小,程序可能需要调用std::swprintf,检查结果值,并重新分配一个更大的缓冲区,然后重试,直到成功。
…或者完全切换到某些其他功能。
- 字符串比较中的意外输出
- 来自 decltype 的意外输出类型
- 字符数组到十六进制字符串的转换 - 意外输出
- 使用 boost::regex 从目录中获取带有一些正则表达式的文件名称时出现意外输出
- 使用后序遍历递归的深度优先搜索会产生意外输出
- C++的意外输出
- 贝金纳C++练习解决方案的意外输出
- 无效* 转换获得意外输出
- 以下程序的意外输出
- 意外输出:矢量矢量(功率集)
- 基本 int 数组提供意外输出
- 线程的意外输出
- 减法中的意外输出
- 带有左移操作员C 的意外输出
- C strncpy意外输出
- 意外输出..函数绑定在虚拟表中的发生方式
- 星号的意外输出
- 已编译的 protobuf 文件的意外输出路径
- 打印对象矢量的意外输出
- 递归函数用于计算 n 个数字之和的意外输出