c++ - swprintf 截断导致意外输出
问题描述
我正在修复在 linux 和 windows 上运行的遗留代码,在某些情况下,应该包含格式化内容的缓冲区小于该内容。
该代码使用 swprintf 根据文档
size - 最多可以写入 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;
}
工作得很好。
怎么了 ?
PS 我希望我可以将所有内容更改为 C++ 流/字符串,但我不能,wchar_t
到处都在使用数组
解决方案
tl; dr:出于某种原因,那些空终止语义依赖于函数调用是否成功,并且swprintf
只有在缓冲区足够大时才会成功。因此,您第一次尝试的数组不是以空值结尾的。
这很微妙,但swprintf
不像snprintf
. 它不会写“最多 N-1 个字符”,并认为在所有情况下都是成功的。
以下是同一文档中关于 from 的返回值的说明swprintf
:
返回值:如果成功则写入的宽字符数(不包括终止的空宽字符),如果发生编码错误或要生成的字符数等于或大于 size(包括 size 为零时),则返回负值
而且,确实,您的尝试返回 -1。
从这个(以及引用下面的注释)我们可以确定如果提供的输出缓冲区中没有足够的字节,则swprintf
认为操作失败。它不会溢出该缓冲区,但它也可能无法完成其工作,其工作包括编写一个 NULL 终止符。如果没有那个 NULL 终止符,wchar_t*
您 [有效地] 传递给std::wcout
将超出范围,并且您的程序具有未定义的行为。
我承认,在随意阅读时,这似乎与围绕size
参数的语义相矛盾,C11 指出:
只
n
写入宽字符,包括一个终止的空宽字符,它总是被添加(除非n
是零)。
…没有说明函数调用是否成功的任何条件。
可能会将此称为标准中的编辑缺陷或实现错误。但即使两者都不是真的,你的函数调用被认为是不成功的,我认为你不应该相应地依赖结果。
我们至少可以从Formatted Output Functions 的手册页中看到libc意图与上述破败相匹配:
返回值是为给定输入生成的字符数,不包括尾随的 null。如果不是所有输出都适合提供的缓冲区,则返回负值。您应该使用更大的输出字符串再试一次。注意:这与 snprintf 处理这种情况的方式不同。
您将不得不注意上述注意事项:
虽然窄字符串提供 std::snprintf,这使得确定所需的输出缓冲区大小成为可能,但宽字符串没有等价物,为了确定缓冲区大小,程序可能需要调用 std::swprintf,检查结果值,并重新分配一个更大的缓冲区,再试一次,直到成功。
…或完全切换到其他功能。
推荐阅读
- javascript - 怎样才能让用户做一次QUIZZ?
- android - 使用毕加索将base64字符串加载到imageview中
- ruby - 如何使用.include?捕获导致相同操作的多个选项
- php - 如何对“私有”构造函数进行例外处理?
- java - 如何在 Java 8 的范围内生成具有指定大小的随机整数的列表?
- c# - Unity3D 引擎类中不应该出现一致的“CS0201”错误
- regex - 如何计算最后出现在单元格中的Google表格单元格中特定字符的出现次数作为标准?
- java - 不兼容的类型。必需:T,找到 T。为什么?
- excel - 如何让 Excel 用户表单验证然后编辑用户表单
- java - SpringBootTest 和 MockMvc 创建了大量控制台日志记录 - 如何禁用?