Search code examples
c++segmentation-faultdeprecatedostream

Inherited CombinedStream object causing segmentation fault


all. I am using a slightly-modified version of the class defined at https://stackoverflow.com/a/1761027/2909854 which allows me to combine streams so that anything sent to the combined stream goes to all of the linked streams. This code works well for except for two issues:

  1. It causes deprecated-declarations warnings (that answer is from 2009).
  2. If I inherit a CombinedStream object, when I use it, I get a segmentation fault.

Code:

#include <iostream>

// Needed for ComposeStream
#include <algorithm>
#include <vector>
#include <fstream>
#include <functional>

// A class that allows us to combine streams so that anything sent to the combined stream goes to all of them
// Modified from https://stackoverflow.com/a/1761027/2909854
class CombinedStream: public std::ostream
{
    struct ComposeBuffer: public std::streambuf
    {
        void addBuffer(std::streambuf* buf)
        {
            bufs.push_back(buf);
        }
        virtual int overflow(int c)
        {
            #pragma GCC diagnostic push
            #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
            std::for_each(bufs.begin(),bufs.end(),std::bind2nd(std::mem_fun(&std::streambuf::sputc),c));
            #pragma GCC diagnostic pop
            return c;
        }

        private:
            std::vector<std::streambuf*>    bufs;

    };
    ComposeBuffer myBuffer;
    public:
        CombinedStream(): std::ostream(NULL)
        {
            std::ostream::rdbuf(&myBuffer);
        }
        void linkStream(std::ostream& out)
        {
            out.flush();
            myBuffer.addBuffer(out.rdbuf());
        }
};

class Parent
{
    public:
        Parent();

    protected:
        std::fstream m_outputFile;
        CombinedStream m_outputFileAndCout;
};

class Child: protected Parent
{
    public:
        Child();
};


Parent::Parent()
{
    std::string filePath = "test_file.test";
    std::cout << "Opening " << filePath << "\n\n";
    std::fstream m_outputFile(filePath, std::ios::app);

    // Create a stream that combines cout and the process log
    m_outputFileAndCout.linkStream(std::cout);
    m_outputFileAndCout.linkStream(m_outputFile);

    std::cout << __FILE__ << ":" << __LINE__ << std::endl;
    m_outputFileAndCout << "I'm working!" << std::endl;
    std::cout << __FILE__ << ":" << __LINE__ << std::endl;
}

Child::Child()
{
    std::cout << __FILE__ << ":" << __LINE__ << std::endl;
    m_outputFileAndCout << "I'm crashing!" << std::endl;
    std::cout << __FILE__ << ":" << __LINE__ << std::endl;
}

int main()
{
    Child object;
    return 0;
}

Compilation:

g++-13 inherited_stream.cpp -o inherited_stream.exe -std=c++20 -g

Questions:

  1. Is it likely that the issues being pointed to by the deprecated-declarations warnings is causing this issue? If so, can you help me update the std::for_each... line to resolve these?
  2. If no to question 1, any ideas why I get the segmentation fault?

Here is a gdb backtrace:

(gdb) run
Starting program: /home/trackingtech/gateway/experiments/inherited_stream.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Opening test_file.test

inherited_stream.cpp:72
I'm working!
inherited_stream.cpp:74
inherited_stream.cpp:79

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x0000aaaaaaaa46a4 in std::mem_fun1_t<int, std::basic_streambuf<char, std::char_traits<char> >, char>::operator() (this=0xffffffffed60, __p=0xffffffffecd8, __x=73 'I') at /usr/include/c++/13/bits/stl_function.h:1309
#2  0x0000aaaaaaaa416c in std::binder2nd<std::mem_fun1_t<int, std::basic_streambuf<char, std::char_traits<char> >, char> >::operator() (this=0xffffffffed60, __x=@0xaaaaaaacc8d8: 0xffffffffecd8)
    at /usr/include/c++/13/backward/binders.h:165
#3  0x0000aaaaaaaa38c8 in std::for_each<__gnu_cxx::__normal_iterator<std::basic_streambuf<char, std::char_traits<char> >**, std::vector<std::basic_streambuf<char, std::char_traits<char> >*, std::allocator<std::basic_streambuf<char, std::char_traits<char> >*> > >, std::binder2nd<std::mem_fun1_t<int, std::basic_streambuf<char, std::char_traits<char> >, char> > > (__first=0xffffffffecd8, __last=0x0, __f=...) at /usr/include/c++/13/bits/stl_algo.h:3833
#4  0x0000aaaaaaaa2cdc in CombinedStream::ComposeBuffer::overflow (this=0xfffffffff158, c=73) at inherited_stream.cpp:23
#5  0x0000fffff7e9f5b4 in std::basic_streambuf<char, std::char_traits<char> >::xsputn(char const*, long) () from /lib/aarch64-linux-gnu/libstdc++.so.6
#6  0x0000fffff7e9159c in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
   from /lib/aarch64-linux-gnu/libstdc++.so.6
#7  0x0000fffff7e91950 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) () from /lib/aarch64-linux-gnu/libstdc++.so.6
#8  0x0000aaaaaaaa29b8 in Child::Child (this=0xffffffffef38) at inherited_stream.cpp:80
#9  0x0000aaaaaaaa2a50 in main () at inherited_stream.cpp:86
(gdb)

Solution

  • In the Parent constructor you define a brand new and local variable m_outputFile and use it instead of the member variable of the same name.

    That means the pointer to the buffer you add to the combined stream will become invalid after the Parent constructor function ends, and the local m_outputFile object is destructed.

    Since you hard-code the filename you should use a constructor initializer list to initialize the member object:

    Parent::Parent()
        : m_outputFile("test_file.test", std::ios::app)  // Initialize the member variable
    {
        // Create a stream that combines cout and the process log
        m_outputFileAndCout.linkStream(std::cout);
        m_outputFileAndCout.linkStream(m_outputFile);
    
        std::cout << __FILE__ << ":" << __LINE__ << std::endl;
        m_outputFileAndCout << "I'm working!" << std::endl;
        std::cout << __FILE__ << ":" << __LINE__ << std::endl;
    }
    

    To solve the warning, instead of temporarily disabling it you can use a lambda instead of std::bind2nd and std::mem_fn:

    std::for_each(bufs.begin(), bufs.end(),
                  [c](std::streambuf* buffer)
                  {
                      buffer->sputc(c);
                  });
    

    Or perhaps better yet, use a range for loop:

    for (auto buffer : bufs)
        buffer->sputc(c);