【问题标题】:std::iostream read or write with count zero and invalid bufferstd::iostream 读取或写入计数为零且缓冲区无效
【发布时间】:2015-09-16 04:59:35
【问题描述】:

以下代码读取一个文件,其中包含一些值,该值表示更多后续数据的长度。

auto file = std::ifstream(filename, std::ios::in | std::ios::binary);
// dataLen = Read some header field containing a length of following data.
std::vector<unsigned char> data;
data.resize(dataLen);
file.read((char*)data.data(), dataLen);

如果 dataLen = 0,MSVC 2013 编译器会失败。它会通过消息Expression: invalid null pointer 导致中止,因为data.data() 返回一个空指针。

This question 表明 count 为 0 对 std::basic_istream::read 有效,但关于该问题的第三条评论似乎指出了我的问题。

将无效指针传递给大小为 0 的 std::basic_istream::read(或 std::basic_ostream::write)是否有效?这对我来说似乎是合乎逻辑的,因为无论如何调用都不应该触及缓冲区。

显而易见的解决方案是使用 if 子句处理这种特殊情况,但我想知道 MSVC 是否又错了。

这是一个编译好的clang运行程序示例:http://coliru.stacked-crooked.com/a/c036ec31abd80f22

【问题讨论】:

  • 为了澄清,“它会导致消息中止......” - 这是由未捕获的异常触发的吗?或者它是 VS 标准 C++ 库的调试二进制文件中的运行时 assertion。可能看起来差别不大,但它是相关的。在 release 构建中是否有相同的行为?
  • @WhozCraig 它说Debug Assertion Failed!,并且不会在发布版本中发生。
  • @typ1232 那么为什么不在调试模式下只做data.resize(dataLen + 1)
  • @Barry 因为以后使用data.size() 不再精确到实际读取数据的大小。虽然后续数据.resize(dataLen); 会解决这个问题,但以上所有内容都是 OP 问题的重点:首先是否需要这样的箍。
  • @typ1232 我也很怀疑。 MS 试图通过告诉您传入的指针无效来“提供帮助”。该断言应该包括对声称的缓冲区大小也非零的验证。在审查标准时,即使没有提取数据,也会对缓冲区进行单字符写入,但这取决于传递的非零长度。但是在您的情况下,所述长度为零,因此不适用。如果 MS 抛出运行时异常,我会觉得这很可悲(对 MS 来说不是不熟悉的领域),但我只能将其归类为普通的恼人。

标签: c++ c++11 iostream ostream istream


【解决方案1】:

这是标准在 27.7.2.3 [istream.unformatted] 第 30 和 31 段中对 std::basic_istream&lt;...&gt;::read() 的说明(重点是我的):

basic_istream<charT,traits>& read(char_type* s, streamsize n);

效果:表现为未格式化的输入函数(如 27.7.2.3 第 1 段所述)。构造sentry对象后,如果!good()调用setstate(failbit)可能会抛出异常,并返回。否则提取字符并将它们存储到 数组的连续位置,该数组的第一个元素由 s 指定。字符被提取并存储,直到发生以下任一情况:

  • 存储n 字符;
  • 文件结束出现在输入序列上(在这种情况下,函数调用setstate(failbit | eofbit),可能会抛出ios_base::failure)。

返回*this.

当函数被描述为以数组为参数时,根据 17.6.4.9 [res.on.arguments] 第 1 段(省略的文本适用于其他实体),可以传递的内容有一些限制:

除非另有明确说明,否则以下各项均适用于 C++ 标准库中定义的函数的所有参数。

  • 如果函数的参数具有无效值(例如函数域之外的值或对其预期用途无效的指针),则行为未定义。
  • 如果函数参数被描述为数组,则实际传递给函数的指针应具有一个值,使得所有地址计算和对对象的访问(如果指针确实指向第一个这种数组的元素)实际上是有效的。
  • ...

根据 8.3.4 [dcl.array] 第 1 段,实际数组不能为空(请注意,不存在常量表达式的情况会产生一个未指定大小的数组,但最终仍会获得非零大小):

... 如果常量表达式存在,它应该是 std::size_t 类型的转换常量表达式,并且它的值应该更大 比零。 ...

由于空指针不能指向非空数组,因此期望传递数组的函数确实需要非空指针。换句话说,我认为您观察到的断言是完全有序的,根据标准将定义的行为赋予具有未定义行为的使用:即使将零大小传递给read() 的空指针也会根据标准产生未定义的行为。

【讨论】:

  • 所以基本上标准允许实现取消引用指针,即使当计数为零时我看不到它的合法使用。很好的答案,谢谢!
猜你喜欢
  • 2016-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-15
  • 1970-01-01
  • 2019-06-30
  • 1970-01-01
  • 2015-08-26
相关资源
最近更新 更多