Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does compile order sometimes cause a segmentation fault when using std::map::insert()?

I have a class called Controller, inside of which, I have a class called Button. A Controller contains several Button instances of different types (e.g. button_type_a, button_type_b).


controller.h

#ifndef __controller__
#define __controller__
class Controller
{
public:
    class Button
    {
    public:
        Button(int type = -1);

    private:
        int type;
    };

    Controller();

    Button A;
    Button B;
    Button X;
    Button Y;
};
#endif


The button types are ints, and I would like to be able to associate certain button type ints with pointers to Button instances of those particular types.

To keep track of this association, I use a std::map<int, Controller::Button*>, which I typedef to a buttonmap_t.

As I create new Button instances (in the Controller constructor), the Button constructor registers the types of those Buttons with the map.


controller.cpp

#include "controller.h"
#include <map>

typedef std::map<int, Controller::Button*> buttonmap_t;
buttonmap_t map;

Controller::Controller() :
A(0),
B(1),
X(2),
Y(3)
{ }

Controller::Button::Button(int type) :
type(type)
{
    map[type] = this;
}


Then I create a global Controller object, and I define main().


main.cpp

#include <iostream>
#include "controller.h"

Controller controller;

int main(int argc, const char * argv[])
{
    std::cout << "running..." << std::endl;
    return 0;
}


Depending on the order in which I compile the sources, the program either runs fine, or triggers a segmentation fault:

apogee:MapTest$ gcc controller.cpp main.cpp -o maptest -lstdc++
apogee:MapTest$ ./maptest 
running...

apogee:MapTest$ gcc main.cpp controller.cpp -o maptest -lstdc++
apogee:MapTest$ ./maptest 
Segmentation fault: 11


It seems like the latter case is trying to use the map before it has been properly initialized, and that's causing a seg fault. When I debug with Xcode, the debugger stops in "__tree" as the std::map is calling __insert_node_at(), which throws EXC_BAD_ACCESS(code=1, address=0x0). The call stack reveals that this was triggered by the first Button instance calling map[type] = this;.

So, here's my multi-part question:

  1. Why does the compile order cause this to happen?
  2. Is there a way to achieve this kind of int to Button* mapping that is unaffected by compile order?
  3. If so, what is it?

Ideally, I'd still like to have all of the Controller- and Button-related code in the separate controller.* files.


It seems like this is somewhat related to (but not quite the same as) the following questions:

  1. Segmentation fault in std::map::insert(...)
  2. [] operator in std::map is giving me segmentation fault
like image 552
camall3n Avatar asked Jan 13 '23 22:01

camall3n


1 Answers

When you have multiple global constructors, the order in which they are executed is undefined. If the controller object is instantiated before the map object is, the controller won't be able to access that map, which results in the segfault. If, however, the map object is instantiated first, then the controller can access it just fine.

In this case, it seems to be the order in which they appear on the command line. That's why putting your main.cpp first caused the segfault - the controller object was getting instantiated first.

I'd recommend moving the instantiation inside your main, because then you can control exactly how objects are instantiated and in what order.

like image 68
Drew McGowen Avatar answered Jan 16 '23 00:01

Drew McGowen