How does ifstream's eof() work?
Asked Answered
A

4

55
#include <iostream>
#include <fstream>

int main() {
    std::fstream inf( "ex.txt", std::ios::in );
    while( !inf.eof() ) {
        std::cout << inf.get() << "\n";
    }
    inf.close();
    inf.clear();
    inf.open( "ex.txt", std::ios::in );
    char c;
    while( inf >> c ) {
        std::cout << c << "\n";
    }
    return 0;
}

I'm really confused about eof() function. Suppose that my ex.txt's content was:

abc

It always reads an extra character and shows -1 when reading using eof(). But the inf >> c gave the correct output which was 'abc'? Can anyone help me explain this?

Agitato answered 26/12, 2010 at 7:21 Comment(1)
Thanks for pointing out that. I was competing on TopCoder, and I usually import all the namespaces in order to save time.Agitato
B
87

-1 is get's way of saying you've reached the end of file. Compare it using the std::char_traits<char>::eof() (or std::istream::traits_type::eof()) - avoid -1, it's a magic number. (Although the other one is a bit verbose - you can always just call istream::eof)

The EOF flag is only set once a read tries to read past the end of the file. If I have a 3 byte file, and I only read 3 bytes, EOF is false, because I've not tried to read past the end of the file yet. While this seems confusing for files, which typically know their size, EOF is not known until a read is attempted on some devices, such as pipes and network sockets.

The second example works as inf >> foo will always return inf, with the side effect of attempt to read something and store it in foo. inf, in an if or while, will evaluate to true if the file is "good": no errors, no EOF. Thus, when a read fails, inf evaulates to false, and your loop properly aborts. However, take this common error:

while(!inf.eof())  // EOF is false here
{
    inf >> x;      // read fails, EOF becomes true, x is not set
    // use x       // we use x, despite our read failing.
}

However, this:

while(inf >> x)  // Attempt read into x, return false if it fails
{
    // will only be entered if read succeeded.
}

Which is what we want.

Balcom answered 26/12, 2010 at 7:38 Comment(5)
Probably should not use >> instead of get(). Remember that the >> operator always skips proceeding white space before attempting to read the next value while get() reads every character one at a time.Countrified
@Thanatos: Sorry to bother you but ... What about streams? You said that EOF is set when we try to read characters past the end of the file but what happens when we try to peek() at the input stream cin when it has no characters? The standard says that traits::eof() is called but peek() seems to wait for an input when I attempt to use it that way.Paris
peek will return EOF if you attempt to peek past the end of the input. However, cin is typically a terminal that you're typing into. (This seems to be your case.) "Waiting for user input" is not the same as an EOF, it's just the source of data (you) is slow. So peek blocks, waiting for you to give it input. If you input EOF (Ctrl+D on Linux), then peek will return, and eof should be true. Otherwise, it returns what you typed.Balcom
I have a question. The doc says that the bool operator is defined as !fail(), so a EOFed stream should be interpreted as true? How comes that it will return false?Butterworth
Question solved. In the failed() link given above, Reaching the End-of-File sets the eofbit. But note that operations that reach the End-of-File may also set the failbit if this makes them fail (thus setting both eofbit and failbit).Butterworth
D
11

The EOF flag is only set after a read operation attempts to read past the end of the file. get() is returning the symbolic constant traits::eof() (which just happens to equal -1) because it reached the end of the file and could not read any more data, and only at that point will eof() be true. If you want to check for this condition, you can do something like the following:

int ch;
while ((ch = inf.get()) != EOF) {
    std::cout << static_cast<char>(ch) << "\n";
}
Descendible answered 26/12, 2010 at 7:29 Comment(12)
"The EOF flag is only set after a read operation reaches the end of the file." is exactly the statement that causes confusing about EOF. The EOF flag is only set after a read operation attempts to read past the end of the file. The last bit is critical: If I read 3 bytes from a file 3 bytes long, EOF is false, until I attempt another read.Balcom
iostream doesn't return an EOF constant. The return value of int istream::get() appears to be undefined at the end of the file -- you have to check istream::eof()Mccarley
@Ken Bloom: Are you sure? My standard says "Returns: c if available, otherwise traits::eof()." 27.6.1.3 (Draft 2, 2 Dec 96)Balcom
<s>You're right, but that's still a far cry from the EOF constant used by C stdio.<s> I'm wrong. Somewhere out there in the definition of char_traits<char> it says that char_traits<char>::eof() returns EOFMccarley
@Ken Bloom Just found section 21.1.3.1 struct char_traits<char>: "The member eof() returns EOF."Descendible
@Justin, I also just found it at the same time you did.Mccarley
@Ken Bloom: My point wasn't so much that it might or might not return EOF - although, good find on that, I did not know that EOF and char_traits<char>::eof() were gaurenteed to be equal - but that its return value was well defined to some constant somewhere.Balcom
@Justin Spahr-Summers: Thanks for a another great solution ;)Agitato
You should still not test for EOF in the condition. What happens if something else goes wrong in the stream and the bad bit gets set. Then the stream will never return EOF.Countrified
@Martin York The EOF flag might not be set, but get() will still return traits::eof(), which is used as a general error code if the read fails.Descendible
@Ken Bloom: where can I find the standard for C++ to reference? Thanks!Agitato
@Chan: you have to buy the standard, but most of us look at the N1905 working draft.Mccarley
M
9

iostream doesn't know it's at the end of the file until it tries to read that first character past the end of the file.

The sample code at cplusplus.com says to do it like this: (But you shouldn't actually do it this way)

  while (is.good())     // loop while extraction from file is possible
  {
    c = is.get();       // get character from file
    if (is.good())
      cout << c;
  }

A better idiom is to move the read into the loop condition, like so: (You can do this with all istream read operations that return *this, including the >> operator)

  char c;
  while(is.get(c))
    cout << c;
Mccarley answered 26/12, 2010 at 7:31 Comment(5)
Should "istream read operations ... including the << operator" read "...including the >> operator" ?Balcom
@Thanatos: yeah. That's what I get for posting late at night.Mccarley
You want !is.fail() instead of is.good(). When reading, e.g., a number from a file, the stream has to reach the end of the file before determining if it read the last digit of the number. In that case, a number will have been successfully read but the end of file will be reached, causing is.good() to return false. This could cause you to skip the last value on the file. Instead, checking for !is.fail() will do a right thing in all cases.Dark
@KenBloom - If the stream in your while(is.get(c)) is binary, won't this prematurely terminate the loop if the value of the byte is 0 (a completely expected value for a binary file?) Wouldn't it be better to use the while(n=is.get() != Traits::eof()) for a binary file?Pennyworth
@smgreenfield: the overload of get that takes an output parameter returns an istream& for further chaining or error reporting.Mccarley
P
2

eof() checks the eofbit in the stream state.

On each read operation, if the position is at the end of stream and more data has to be read, eofbit is set to true. Therefore you're going to get an extra character before you get eofbit=1.

The correct way is to check whether the eof was reached (or, whether the read operation succeeded) after the reading operation. This is what your second version does - you do a read operation, and then use the resulting stream object reference (which >> returns) as a boolean value, which results in check for fail().

Phifer answered 26/12, 2010 at 7:35 Comment(1)
It checks several bits actually.Countrified

© 2022 - 2024 — McMap. All rights reserved.