【问题标题】:swprintf truncation causes unexpected outputswprintf 截断导致意外输出
【发布时间】:2019-02-06 08:35:02
【问题描述】:

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

代码使用根据documentation的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;
}

工作得很好。

怎么了?

附: 我希望我可以将所有内容更改为 C++ 流/字符串,但我不能,wchar_t 数组无处不在

【问题讨论】:

  • 使用大量数组并不意味着不能使用流 I/O。 std::ostream 有一个 write() 方法,您可以将(一块)数组传递给
  • 我的意思是,标准库流 IO 并不是 API 设计的典范。
  • @Spinkoo 很好,swprintf() 采用缓冲区大小进行边界检查和截断。听起来更像是 OP 的 STL 库在其 swprintf() 实现中有一个错误,如果缓冲区填充到允许的最大字符,它不会正确写出终止 null。
  • 啊,是的,我同意你的看法
  • @RemyLebeau 我不相信这是一个错误,尽管 IMO 它在 C 中的指定不足。请参阅我的回答 - 欢迎提出想法!

标签: c++ printf


【解决方案1】:

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


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

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

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

事实上,your attempt returns -1

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


我承认,这似乎与围绕 size 参数的语义相矛盾,C11 声明:

写入的宽字符不超过n,包括一个终止的空宽字符,它总是被添加(除非n 为零)。

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

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

我们至少可以从this manual page on Formatted Output Functions 看到 libc 意图与上面的破败相匹配:

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


您将不得不注意上述注意事项:

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

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

【讨论】:

  • 是的,我明白这可能是一种解释,当你说其他功能时,你的意思是还有另一种可移植的方式来复制带有截断的wchar_t* based 字符串?
  • @Darius 老实说,我没有研究可能的替代方案 - 留给读者作为练习,抱歉;)
  • @Darius 可能有一些想法here(有人正在寻找所说的替代方案);至少,他们(以及链接的 comp.std.c 讨论中的人们)同意存在问题。
猜你喜欢
  • 2020-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多