views:

46

answers:

2

In my program, I've redirected stdout to print to a file 'console.txt'. A function writes to that file like this:

void printToConsole(const std::string& text, const TCODColor& fc, const TCODColor& bc)
    {
        // write the string
        cout << text << "@";

        // write the two color values
        cout << static_cast<int>(fc.r) << " "
             << static_cast<int>(fc.g) << " " 
             << static_cast<int>(fc.b) << " "
             << static_cast<int>(bc.r) << " "
             << static_cast<int>(bc.g) << " " 
             << static_cast<int>(bc.b) << " " << endl;
    }

I have a function that reads from that file that looks like this:

   void Console::readLogFile()
   {
        ifstream log("console.txt", ifstream::in);
        if(!log.is_open())
        {
            cerr << "ERROR: console.txt not found!" << endl;
            return;
        }

        // read new input into the stack
        char str[256];
        while(!log.eof())
        {
            log.getline(str, 256);
            cerr << "str: " << str << endl;
            stk.push(static_cast<string>(str));
            // stk is a std::stack<std::string> member of the class this function
            // belongs to.
        }
        cerr << endl;

        /* Do some stuff with str and stk here */

        log.close();
        clearLogFile();
    }

    void Console::clearLogFile()
    {
        FILE* log;
        log = fopen("console.txt", "w");
        fclose(log);
    }

Often, console.txt is empty when readLogFile is called. I would expect that the while(!log.eof()) loop would never execute in that case, but it does. There is always at least one extraneous blank line in the file, sometimes two, and when input is read from the file, the input line is sandwiched between two blank lines. After a few calls to this function, the while(!log.eof()) loop then goes into an infinite loop pulling blank lines from the file. A typical runthrough of the program looks like this:

str: 

str: Player moved.@191 191 191 0 0 0
str: 

str: 
str: Player moved.@191 191 191 0 0 0 
str: 

str: // there should be a 'Player moved.' line in here
str:

str: // here as well
str:

str: // also here
str:

str: 
str: Player moved.@191 191 191 0 0 0 
str: 

str:
str:
str:
str:
str:
str:
(onto infinite loop)

Can anyone see what I'm doing wrong here?

EDIT: As Amardeep suggested, I changed the while(!log.eof()) loop to a do{...}while(!log.fail); loop. This fixed the infinite loop problem, but not the extraneous lines. The program behaves as before, except where it once went into the infinite loop, it now reads nothing but blank lines where it should read input, like this:

str:

str:

str:

str: 
(etc.)
A: 

eof() status is not set until you attempt a read. You should change your read loop to do the getline() then check the fail() status instead of relying on eof(), which doesn't cover the breadth of things that can go wrong trying to read the file.

Amardeep
I changed the `while(!log.eof())` to a `do{...}while(!log.fail());` loop, and this fixed the infinite loop problem but it's still reading in extraneous blank lines.
Max
You need to check for `fail` after reading but before calling `push`. If you don't want an ugly if statement in the middle of your loop, you could do this by changing your code to be `log.getline(str, 256);while(!log.fail()) {cerr << "str: " << str << endl;stk.push(static_cast<string>(str));log.getline(str, 256);}`
Brian
This almost fixed my problem. The read loop is okay now, I believe, the problem is now with my `clearLogFile` function. If I comment it out, it all behaves as I would expect (although it reads old data since the file isn't cleared out), with it still in, there are extraneous blank lines before each line of real input, and after a while it stops reading in input all together (apparently the check for `fail` returns true right off the bat).
Max
I would suggest changing your `clearLogFile()` function to use streams instead of the C library calls. There might be some caching that is causing a race condition between the streams close and the fopen(). `ofstream("console.txt", ios_base::trunc);` should do it.
Amardeep
Nope, still the same. Changed anyway, just for consistency.EDIT: now that you mentioned it, though, I redirected stdout by calling `freopen()` on a `FILE` pointer. Could this have something to do with it? Is there a way to redirect stdout using streams? I couldn't find any with a cursory google search.
Max
Um, redirecting by itself should not be a problem. But are these processes happening concurrently? Is something writing to the file while you call `clearLogFile()` ? You're going to have to do some synchronization if that's the case.
Amardeep
No, my program is single threaded, and `readLogFile()` is currently the only place where `clearLogFile()` is called.
Max
I think I found what's causing it. I've been stepping through my program and checking the contents of console.txt with gvim as I go along, and when when my file should be cleared, it instead replaces the old data with a long string of `^@^@^@^@^@^@^@^@^@^@^@^@^@`, after which is placed the first new line of input. This somehow makes `getline` read that first line as a blank string. Any subsequent data entered into console.txt isn't prefixed by `^@^@^@` and is read fine. Any ideas what's causing this garbage data?
Max
Well, this question looks like it's died out. Most of my problem was addressed anyway, so I'll mark this one as answered and ask a more specific question tomorrow. Cheers, everyone. Thanks for your help.
Max
Glad to be of some small assistance.
Amardeep
+1  A: 

Standard anti patter for reading a file.

    while(!log.eof())
    {
        log.getline(str, 256);
        cerr << "str: " << str << endl;
        stk.push(static_cast<string>(str));
        // stk is a std::stack<std::string> member of the class this function
        // belongs to.
    }

try this:

    while(log.getline(str, 256))
    {
        cerr << "str: " << str << endl;
        stk.push(string(str));
    }

This works because the getline() method returns a reference to the stream.

When a stream is used in a boolean context it gets converted into a bool (for the pedantic not really but for the beginner as good as). If the stream is still in a good state after the read (ie the read worked) then it is converted to true. If the stream is in a bad state (ie the read failed) then it is converted to false. Therefore if the read worked the loop is entered. If the read failed (because maybe EOL is read) then the loop is not entered.

Note your version failed because you did not test the eof() after the read (getline()). The is because the last good read reads all the characters upto the EOF. But this means the eof flag is not set. It is not until you try to actually read past the EOF (this only happens if you read something after the last read read all the other characters) that the EOF flag is set.

PS. There is a free function that reads from a stream into a string.

std::string line;
std::getline(log, line);
Martin York
Alright, thanks. This didn't quite fix my problem, but it did make my code better. I'll keep that in mind from now on.
Max