-1

I wrote the following C++ program to read a text file line by line and print out the content of the file line by line. I entered the name of the text file as the only command line argument into the command line.

#include <iostream>
#include <fstream>
using namespace std;

int main(int argc, char* argv[])
{
    char buf[255] = {};
    if (argc != 2)
    {
        cout << "Invalid number of files." << endl;
        return 1;
    }
    ifstream f(argv[1], ios::in | ios::binary);
    if (!f)
    {
        cout << "Error: Cannot open file." << endl;
        return 1;
    }

    while (!f.eof())
    {
        f.get(buf,255);
        cout << buf << endl;
    }
    f.close();
    return 0;
}

However, when I ran this code in Visual Studio, the Debug Console was completely blank. What's wrong with my code?

  • HINT: What do C and C++ print functions expect at the end of a string? How long is the first line of your input? What is in `buf` at startup? – Daniel Dearlove Aug 09 '21 at 14:24
  • 1
    Also, you must test for eof / check the stream's state *after* the input operation. – Peter - Reinstate Monica Aug 09 '21 at 15:05
  • If I read the documentation correctly, istream::get extracts up to n-1 characters from the stream -- in your case, up to 254 -- *and then appends a null byte* for which your buffer will have no room. In order to avoid reading and correctly understanding the fineprint simply have the buffer so large that it's OK either way, and zero it before use, so that you don't need to know whether the function adds a null byte, and whether that's included in the count or not. – Peter - Reinstate Monica Aug 09 '21 at 15:10
  • 2
    Does this answer your question? [Why is iostream::eof inside a loop condition (i.e. \`while (!stream.eof())\`) considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons) – Superlokkus Aug 09 '21 at 15:19
  • 1
    @Superlokkus: Though that is incorrect usage, it was not the issue here. I tried to use a while loop counter myself and the reading get stuck in the first part. – Sreeraj Chundayil Aug 09 '21 at 15:21
  • 1
    Since you are using Visual Studio, why on Earth don't you use its wonderful debugging facilities!? Go to the line with the get() and press F9; that will cause execution so pause there. Press F10 to execute the next statement; you'll see that your debug console shows something, and that the buffer contains text (provided the file was found). If you continue to loop you'll find that the buffer stays empty on subsequent iterations. Why? I'll explain it in my upcoming answer ;-). – Peter - Reinstate Monica Aug 09 '21 at 15:22
  • @Peter-ReinstateMonica: Like OP, I am eagerly waiting for the answer too. – Sreeraj Chundayil Aug 09 '21 at 15:23
  • @Peter-ReinstateMonica I modified my program and changed the initialisation of the character array to ```char buff[255] = { };```, but it did not solve my problem. – YuanLinTech Aug 09 '21 at 15:23
  • @YuanLinTech: As a temporary fix, you may try `char ch; f.get(ch); cout< – Sreeraj Chundayil Aug 09 '21 at 15:24
  • Thanks @Peter-ReinstateMonica. I looked again at the [`std::basic_istream::get`](https://en.cppreference.com/w/cpp/io/basic_istream/get). Appending the null character is really hidden in the details. – Daniel Dearlove Aug 09 '21 at 15:25
  • @DanielDearlove That's why I quoted [cplusplus.com](https://www.cplusplus.com/reference/istream/istream/get/). It is clearer. – Peter - Reinstate Monica Aug 09 '21 at 15:33
  • @Peter-ReinstateMonica, thanks for reminding me why I should use `std::basic_istream::getline` in this example. I upvoted your answer. You deserve it. – Daniel Dearlove Aug 09 '21 at 15:43
  • 1
    @Daniel well, actually you should use getline(istream&, string)... – Peter - Reinstate Monica Aug 09 '21 at 16:30

1 Answers1

3

Apart from the errors mentioned in the comments, the program has a logical error because istream& istream::get(char* s, streamsize n) does not do what you (or I, until I debugged it) thought it does. Yes, it reads to the next newline; but it leaves the newline in the input!

The next time you call get(), it will see the newline immediately and return with an empty line in the buffer, for ever and ever.

The best way to fix this is to use the appropriate function, namely istream::getline() which extracts, but does not store the newline.

The EOF issue

is worth mentioning. The canonical way to read lines (if you want to write to a character buffer) is

  while (f.getline(buf, bufSz))
  {
    cout << buf << "\n";
  }

getline() returns a reference to the stream which in turn has a conversion function to bool, which makes it usable in a boolean expression like this. The conversion is true if input could be obtained. Interestingly, it may have encountered the end of file, and f.eof() would be true; but that alone does not make the stream convert to false. As long as it could extract at least one character it will convert to true, indicating that the last input operation made input available, and the loop will work as expected.

The next read after encountering EOF would then fail because no data could be extracted: After all, the read position is still at EOF. That is considered a read failure. The condition is wrong and the loop is exited, which was exactly the intent.

The buffer size issue

is worth mentioning, as well. The standard draft says in 30.7.4.3:

Characters are extracted and stored until one of the following occurs:

  1. end-of-file occurs on the input sequence (in which case the function calls setstate(eofbit));
  2. traits::eq(c, delim) for the next available input character c (in which case the input character is extracted but not stored);
  3. n is less than one or n - 1 characters are stored (in which case the function calls setstate( failbit)).

The conditions are tested in that order, which means that if n-1 characters have been stored and the next character is a newline (the default delimiter), the input was successful (and the newline is extracted as well).

This means that if your file contains a single line 123 you can read that successfully with f.getline(buf, 4), but not a line 1234 (both may or may not be followed by a newline).

The line ending issue

Another complication here is that on Windows a file created with a typical editor will have a hidden carriage return before the newline, i.e. a line actually looks like "123\r\n" ("\r" and "\n" each being a single character with the values 13 and 10, respectively). Because you opened the file with the binary flag the program will see the carriage return; all lines will contain that "invisible" character, and the number of visible characters fitting in the buffer will be one shorter than one would assume.

The console issue ;-)

Oh, and your Console was not entirely empty; it's just that modern computers are too fast and the first line which was probably printed (it was in my case) scrolled away faster than anybody could switch windows. When I looked closely there was a cursor in the bottom left corner where the program was busy printing line after line of nothing ;-).

The conclusion

  • Debug your programs. It's very easy with VS.
  • Use getline(istream, string).
  • Use the return value of input functions (typically the stream) as a boolean in a while loop: "As long as you can extract any input, use that input."
  • Beware of line ending issues.
  • Consider C I/O (printf, scanf) for anything non-trivial (I didn't discuss this in my answer but I think that's what many people do).
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • True, I changed the delimiter and it worked. +1 – Sreeraj Chundayil Aug 09 '21 at 15:32
  • Ouch! Also in the documentation ([here](https://en.cppreference.com/w/cpp/io/basic_istream/get), section 4, third bullet point) but you still need to digest it to understand its significance. Also [here](https://www.cplusplus.com/reference/istream/istream/get/) in the "(3) stream buffer" section. – Daniel Dearlove Aug 09 '21 at 15:39