Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What can go wrong if cout.rdbuf() is used to switch buffer and never set it back?

The author presented this code under the title A bus error on my platform

#include <fstream>
#include <iostream>

int main()
{
    std::ofstream log("oops.log");
    std::cout.rdbuf(log.rdbuf());
    std::cout << "Oops!\n";
    return 0;
}

The string "Oops!\n" is printed to the file "oops.log". The code doesn't restore cout's streambuf, but VS2010 didn't report a runtime error.

like image 453
John Kalane Avatar asked Feb 13 '13 18:02

John Kalane


3 Answers

Since log and std::cout share a buffer, that buffer will probably be freed twice (once when log goes out of scope, then once more when the program terminates).

This results in undefined behavior, so it's hard to tell the exact reason why it triggers a bus error on his machine but silently fails on yours.

like image 58
Frédéric Hamidi Avatar answered Nov 08 '22 20:11

Frédéric Hamidi


Since the other answers don't mention what to do about this I'll provide that here. You need to save and restore the buffer that cout is supposed to be managing. For example:

#include <fstream>
#include <iostream>

// RAII method of restoring a buffer
struct buffer_restorer {
    std::ios &m_s;
    std::streambuf *m_buf;

    buffer_restorer(std::ios &s, std::streambuf *buf) : m_s(s), m_buf(buf) {}
    ~buffer_restorer() { m_s.rdbuf(m_buf); }
};

int main()
{
    std::ofstream log("oops.log");
    buffer_restorer r(std::cout, std::cout.rdbuf(log.rdbuf()));
    std::cout << "Oops!\n";
    return 0;
}

Now when cout's buffer is replaced before cout is destroyed at the end of the program, so when cout destroys its buffer the correct thing happens.


For simply redirecting standard io generally the environment already has the ability to do that for you (e.g., io redirection in the shell). Rather than the above code I'd probably simply run the program as:

yourprogram > oops.log

Also one thing to remember is that std::cout is a global variable with all the same downsides as other global variables. Instead of modifying it or even using it you may prefer to use the usual techniques to avoid global variables all together. For example you might pass a std::ostream &log_output parameter around and use that instead of having code use cout directly.

like image 22
bames53 Avatar answered Nov 08 '22 22:11

bames53


Your program has Undefined Behavior.

The destructor of the global cout object will delete the stream buffer when going out of scope, and the same is true of log, which also owns that very same stream buffer. Thus, you are deleting the same object twice.

When a program has Undefined Behavior, anything could happen, from formatting your hard drive to terminating without any error.

On my platform, for instance, the program enters an infinite loop after returning from main().

like image 3
Andy Prowl Avatar answered Nov 08 '22 20:11

Andy Prowl