std::iostream读取或写入计数为零且缓冲区无效

std::iostream read or write with count zero and invalid buffer

本文关键字:缓冲区 无效 读取 iostream std      更新时间:2023-10-16

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

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编译器一起失败。由于data.data()返回一个空指针,因此会导致消息Expression: invalid null pointer中止。

这个问题表明0的countstd::basic_istream::read有效,但对这个问题的第三条评论似乎指出了我的问题。

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

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

以下是clang运行程序fine的编译示例:http://coliru.stacked-crooked.com/a/c036ec31abd80f22

以下是标准在27.7.2.3[isream.unformated]第30段和第31段中对std::basic_istream<...>::read()的说明(重点是我的):

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

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

  • 存储CCD_ 13个字符
  • 文件结尾出现在输入序列上(在这种情况下,函数调用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类型的转换常量表达式,并且其值应更大大于零。。。

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