简体   繁体   English

为什么编译顺序有时会在使用std :: map :: insert()时导致分段错误?

[英]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 . 我有一个名为Controller的类,其中有一个名为Button的类。 A Controller contains several Button instances of different types (eg button_type_a , button_type_b ). Controller包含几个不同类型的Button实例(例如button_type_abutton_type_b )。


controller.h 或者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 int s, and I would like to be able to associate certain button type int s with pointers to Button instances of those particular types. 按钮类型是int ,我希望能够将某些按钮类型int与指向这些特定类型的Button实例的指针相关联。

To keep track of this association, I use a std::map<int, Controller::Button*> , which I typedef to a buttonmap_t . 为了保持这种关联的赛道,我使用std::map<int, Controller::Button*>这是我typedefbuttonmap_t

As I create new Button instances (in the Controller constructor), the Button constructor registers the types of those Button s with the map. 当我创建新的Button实例(在Controller构造函数中)时, Button构造函数使用map注册这些Button的类型。


controller.cpp 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() . 然后我创建一个全局Controller对象,并定义main()


main.cpp 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. 似乎后一种情况是在正确初始化之前尝试使用地图,这导致了seg故障。 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) . 当我使用Xcode调试时,调试器在“__tree”中停止,因为std::map正在调用EXC_BAD_ACCESS(code=1, address=0x0) __insert_node_at() ,这会抛出EXC_BAD_ACCESS(code=1, address=0x0) The call stack reveals that this was triggered by the first Button instance calling map[type] = this; 调用堆栈显示这是由第一个Button实例调用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? 有没有办法实现这种intButton*映射是由编译秩序的影响?
  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. 理想情况下,我仍然希望将所有与ControllerButton相关的代码放在单独的控制器。*文件中。


It seems like this is somewhat related to (but not quite the same as) the following questions: 看起来这与以下问题有些相关(但不完全相同):

  1. Segmentation fault in std::map::insert(...) std :: map :: insert(...)中的分段错误
  2. [] operator in std::map is giving me segmentation fault std :: map中的[]运算符给我分段错误

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. 如果在map对象之前实例化controller对象,则控制器将无法访问该映射,从而导致段错误。 If, however, the map object is instantiated first, then the controller can access it just fine. 但是,如果首先实例化map对象,那么控制器可以很好地访问它。

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. 这就是为什么把你的main.cpp放在第一位导致段错误的原因 - controller对象首先被实例化了。

I'd recommend moving the instantiation inside your main , because then you can control exactly how objects are instantiated and in what order. 我建议在main移动实例化,因为这样你就可以精确控制对象的实例化方式和顺序。

The initialization order of static variables is not defined so it depends on your particular setup, including compiler, linker and linking order. 静态变量的初始化顺序未定义,因此它取决于您的特定设置,包括编译器,链接器和链接顺序。

The Initializer Function Trick 初始化函数技巧

You can use the initializer function trick to ensure that something is initialized when needed. 您可以使用初始化函数技巧来确保在需要时初始化某些内容。 I know that this works for certain on Windows with Microsoft's C++ compiler (and did a little testing on g++ with Linux, see below). 我知道这在Windows上使用Microsoft的C ++编译器是有效的(并且对使用Linux的g ++进行了一些测试,见下文)。

Initializer Function 初始化函数

The first step is to move the map into a function as a static variable and always access the map through this function. 第一步是将地图作为静态变量移动到一个函数中,并始终通过此函数访问地图。

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

Usage 用法

The map is created when the buttonMap() function is first called. 首次调用buttonMap()函数时会创建映射。 If you access the map through the function then you can be sure that it will be created. 如果您通过该功能访问地图,那么您可以确定它将被创建。

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

The crucial part is the initialization of the global variable: you replace it with a reference and initialize it from the function that holds the variable. 关键部分是全局变量的初始化:用引号替换它并从保存变量的函数初始化它。

buttonmap_t& map = buttonMap();

Explanation 说明

With this setup the initialization order doesn't matter because the first call to the function will perform the initialization and every call after it will use the initialized instance. 使用此设置初始化顺序无关紧要,因为对函数的第一次调用将执行初始化,并且每次调用之后将使用初始化实例。

Note: this trick works for global variables because the initialization phase is done on a single thread. 注意:此技巧适用于全局变量,因为初始化阶段是在单个线程上完成的。 Even if you don't know the exact order of initialization, you can be sure that it will happen sequentially. 即使您不知道初始化的确切顺序,也可以确保它将按顺序发生。

Testing 测试

I tested this on my home computer with g++ on Linux and it seems to work: 我在家用电脑上使用g ++在Linux上进行了测试,它似乎有效:

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

the 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;
}

As @Drew McGowen and @jrok suggested, I checked out the "static initialization order fiasco": 正如@Drew McGowen@jrok建议的那样,我检查了“静态初始化顺序惨败”:
http://www.parashift.com/c++-faq/static-init-order.html http://www.parashift.com/c++-faq/static-init-order.html
http://www.parashift.com/c++-faq/static-init-order-on-first-use.html http://www.parashift.com/c++-faq/static-init-order-on-first-use.html

Using the "construct on first use" idiom, I made the following modifications: 使用“首次使用时构造”的习语,我做了以下修改:


controller.cpp 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 as intended for either compile order, and it doesn't require any changes to "controller.h" or "main.cpp". 这适用于编译顺序,并且不需要对“controller.h”或“main.cpp”进行任何更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM