Search code examples
c++11gcc4.8

Segmentation fault when calling bound member function


I have a class that calls a function depending on a value passed in. The function is void with no parameters and is stored in a map (along with some other information).

The program compiles and the function golden_retriever works as expected but when labrador is called the program SIGSEVs with the following information in gdb (beyond #5 it is out of the test class and into the actual code):

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) where
#0  0x0000000000000000 in ?? ()
#1  0x000000000040dc71 in std::_Mem_fn<void (TestHandlerTwo::*)()>::operator()<, void>(TestHandlerTwo*) const (this=0x6416c0, __object=0x641400)
    at /usr/include/c++/4.8/functional:601
#2  0x000000000040d600 in std::_Bind<std::_Mem_fn<void (TestHandlerTwo::*)()> (TestHandlerTwo*)>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) (this=0x6416c0, 
    __args=<unknown type in /home/master/splint/SplintApp/test, CU 0x1eee, DIE 0x140c8>)
    at /usr/include/c++/4.8/functional:1296
#3  0x000000000040c90c in std::_Bind<std::_Mem_fn<void (TestHandlerTwo::*)()> (TestHandlerTwo*)>::operator()<, void>() (this=0x6416c0) at /usr/include/c++/4.8/functional:1355
#4  0x000000000040bcf3 in std::_Function_handler<void (), std::_Bind<std::_Mem_fn<void (TestHandlerTwo::*)()> (TestHandlerTwo*)> >::_M_invoke(std::_Any_data const&) (
    __functor=...) at /usr/include/c++/4.8/functional:2071
#5  0x000000000040ab5c in std::function<void ()>::operator()() const (this=0x641690)
    at /usr/include/c++/4.8/functional:2471

The code:

#include <iostream>
#include <map>
#include <memory>

struct command
{
    std::string cmdname;            // console friendly name
    std::function<void()> execute;  // function to call
};

class IHandler
{
public:
    virtual void parse(int value) = 0;
};

class BaseHandler : public IHandler
{
public:
    virtual auto getCommandMap() -> std::map<int, command> const = 0;

    void parse(int value) override
    {
        // this normally takes a stream of bytes and parses it but for this example we hardcode it.
        auto search = getCommandMap().find(value);
        if (search == getCommandMap().end())
        {
            return;
        }
        std::cout << "Function is " << (search->second.execute ? "callable" : "not callable") << std::endl;
        if (search->second.execute)
        {
            search->second.execute();
        }
        return;
    }
};

void golden_retriever()
{
    std::cout << "Chases cat" << std::endl;
}
class TestHandlerTwo : public BaseHandler
{
    std::map<int, command> commandMap = 
    {
        { 0x02, { "Handled", golden_retriever } },
        { 0x03, { "Test", std::bind(&TestHandlerTwo::labrador, this) } }
    };
public:
    void labrador()
    {
        std::cout << "Chases birds" << std::endl;
    }
    virtual auto getCommandMap() -> std::map<int, command> const override
    {
        return commandMap;
    }   
};

int main(int argc, char* argv[]) 
{
    auto testHandler = std::shared_ptr<IHandler>(new TestHandlerTwo());
    testHandler->parse(0x02);
    testHandler->parse(0x03);
    return 0;
}

The output of which is:

(gdb) run
Starting program: /home/master/test/main 
Function is callable
Chases cat
Function is callable

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()

My usage of bind seems correct according to this article and question already asked on StackOverflow, so what is wrong with my code?


Solution

  • You are accessing the iterator after the container (map) has destroyed.

    At BaseHandler::parse

    void parse(int value) override
    {
        // !!here, you constructs a map, and destruct it immediately
        // which invalidates the iterator
        // auto search = getCommandMap().find(value);
        // if (search == getCommandMap().end())
        // {
        //     return;
        // }
        // change to these three lines
        auto&& commandMap = getCommandMap();
        auto&& search = commandMap.find(value);
        if (search == commandMap.end()) return;
    
        std::cout << "Function is " << (search->second.execute ? "callable" : "not callable") << std::endl;
        if (search->second.execute)
        {
            search->second.execute();
        }
        return;
    }
    

    See BaseHandler::getCommandMap

    // this always create a copy of original map, which considered
    // temporary, destroys after execution of the statement if not
    // being explicitly held.
    virtual auto getCommandMap() -> std::map<int, command> const = 0;