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

I have a class called Controller , inside which I have a class called Button . A Controller contains several Button instances for 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 types of buttons are int s, and I would like to be able to associate a specific type of button int with pointers to Button instances of these specific types.

To track this connection, I use std::map<int, Controller::Button*> , which I typedef is equal to buttonmap_t .

When I create new Button instances (in the Controller constructor), the Button constructor registers the types of these Button 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() .


<b> 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 works fine or causes a segmentation error:

 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 that the latter case is trying to use the card before it was properly initialized and caused a seg error. When I debug Xcode, the debugger stops at "__tree" when std::map calls __insert_node_at() , which EXC_BAD_ACCESS(code=1, address=0x0) . The call stack shows that this is caused by the first Button instance calling map[type] = this; .

So here is my multi-part question:

  • Why does the compilation order cause this?
  • Is there a way to achieve this kind of display int to Button* , which is not affected by the compile order?
  • If so, what is it?

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


This seems to be somewhat related to (but not quite the same) the following questions:

+4
source share
3 answers

The initialization order of static variables is not defined, so it depends on your specific setup, including the compiler, linker, and binding order.

Trigger function initialization

You can use the initialization function trick to make sure that something is initialized when necessary. I know this probably works on Windows with the Microsoft C ++ compiler (and tested g ++ with Linux a bit, see below).

Initialization function

The first step is to move the map into the function as a static variable and always access the map through this function.

 buttonmap_t& buttonMap() { static buttonmap_t map; return map; } 

Using

The map is created the first time the buttonMap() function is buttonMap() . If you access the map through a function, you can be sure that it will be created.

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

The most important part is the initialization of the global variable: you replace it with a reference and initialize it from the function that contains the variable.

 buttonmap_t& map = buttonMap(); 

Description

With this setting, the initialization order does not matter, since the first call to the function will perform the initialization and each call after it uses the initialized instance.

Note. This trick works for global variables since the initialization phase is performed in a single thread. Even if you do not know the exact initialization order, you can be sure that this will happen sequentially.

Testing

I tested this on my home computer with g ++ on Linux and it seems to work:

 $ g++ main.cpp controller.cpp -Wall $ ./a.out running... 

final program:

 // controller.h #ifndef CONTROLLER_H #define CONTROLLER_H class Controller { public: class Button { public: Button(int type = -1); private: int type_; }; Controller(); Button A; Button B; Button X; Button Y; }; #endif // controller.cpp #include "controller.h" #include <map> typedef std::map<int, Controller::Button*> buttonmap_t; buttonmap_t& ButtonMap() { static buttonmap_t map; return map; } buttonmap_t& map = ButtonMap(); Controller::Controller() : A(0), B(1), X(2), Y(3) { } Controller::Button::Button(int type) : type_(type) { ButtonMap()[type] = this; } // main.cpp #include "controller.h" #include <iostream> Controller controller; int main(int argc, const char * argv[]) { std::cout << "running..." << std::endl; return 0; } 
+2
source

When you have several global constructors, the order of their execution is undefined. If the controller object is created before the map object, the controller will not be able to access this map, which will result in segfault. If, however, the map object is created first, then the controller can access it simply.

In this case, it seems to be the order in which they appear on the command line. Therefore, first of all, so that your main.cpp calls segfault - the controller object receives the instance first.

I would recommend moving the instance inside your main , because then you can precisely control how objects are created and in what order.

+6
source

As @Drew McGowen and @jrok suggested , I checked the "static initialization order fiasco":
http://www.parashift.com/c++-faq/static-init-order.html
http://www.parashift.com/c++-faq/static-init-order-on-first-use.html

Using the idiom โ€œfirst-use designโ€, I made the following changes:


Controller.cpp

 typedef std::map<int, Controller::Button*> buttonmap_t; static buttonmap_t& map() // was buttonmap_t map; { static buttonmap_t* ans = new buttonmap_t(); return *ans; }; //[...] Controller::Button::Button(int type) : type(type) { map()[type] = this; } 


This works both for compilation and for any changes to "controller.h" or "main.cpp".

0
source

Source: https://habr.com/ru/post/1491196/


All Articles