typ1232
typ1232

Reputation: 5607

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

The following code reads a file containing some value that represents the length of more following data.

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);

It fails with the MSVC 2013 compiler if dataLen = 0. It causes an abort with the message Expression: invalid null pointer, because data.data() returns a null pointer.

This question suggests that a count of 0 is valid for std::basic_istream::read, but the third comment on the question seems to point out my issue.

Is it valid C++ to pass an invalid pointer to std::basic_istream::read (or std::basic_ostream::write) with a size of 0? It would seem logical to me, because the call should not touch the buffer anyway.

The obvious solution is to deal with this special case with an if clause, but I am wondering if MSVC is wrong once again.

Here is a compiled example of clang running the program fine: http://coliru.stacked-crooked.com/a/c036ec31abd80f22

Upvotes: 2

Views: 874

Answers (1)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153955

Here is what the standard says about std::basic_istream<...>::read() in 27.7.2.3 [istream.unformatted] paragraphs 30 and 31 (emphasis is mine):

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

Effects: Behaves as an unformatted input function (as described in 27.7.2.3, paragraph 1). After constructing a sentry object, if !good() calls setstate(failbit) which may throw an exception, and return. Otherwise extracts characters and stores them into successive locations of an array whose first element is designated by s. Characters are extracted and stored until either of the following occurs:

  • n characters are stored;
  • end-of-file occurs on the input sequence (in which case the function calls setstate(failbit | eofbit), which may throw ios_base::failure).

Returns: *this.

When a function is described as taking array as argument, there are some constraints on what can be passed according to 17.6.4.9 [res.on.arguments] paragraph 1 (elided text applies to other entities):

Each of the following applies to all arguments to functions defined in the C++ standard library, unless explicitly stated otherwise.

  • If an argument to a function has an invalid value (such as a value outside the domain of the function or a pointer invalid for its intended use), the behavior is undefined.
  • If a function argument is described as being an array, the pointer actually passed to the function shall have a value such that all address computations and accesses to objects (that would be valid if the pointer did point to the first element of such an array) are in fact valid.
  • ...

Actual arrays cannot be empty according to 8.3.4 [dcl.array] paragraph 1 (note that the case where the constant expression is absent yields an array of unspecified size which still gets a non-zero size eventually):

... If the constant-expression is present, it shall be a converted constant expression of type std::size_t and its value shall be greater than zero. ...

Since a null pointer cannot point to a non-empty array functions expecting an array being passed do expect a non-null pointer. Put differently, I think the assertion you observed is entirely in order, giving defined behavior to a use which has undefined behavior according to the standard: a null pointer even with a zero size passed to read() yield undefined behavior according to the standard.

Upvotes: 1

Related Questions