繁体   English   中英

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

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

我有一个名为Controller的类,其中有一个名为Button的类。 Controller包含几个不同类型的Button实例(例如button_type_abutton_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


按钮类型是int ,我希望能够将某些按钮类型int与指向这些特定类型的Button实例的指针相关联。

为了保持这种关联的赛道,我使用std::map<int, Controller::Button*>这是我typedefbuttonmap_t

当我创建新的Button实例(在Controller构造函数中)时, Button构造函数使用map注册这些Button的类型。


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


然后我创建一个全局Controller对象,并定义main()


main.cpp中

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

Controller controller;

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


根据编译源的顺序,程序运行正常,或触发分段错误:

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


似乎后一种情况是在正确初始化之前尝试使用地图,这导致了seg故障。 当我使用Xcode调试时,调试器在“__tree”中停止,因为std::map正在调用EXC_BAD_ACCESS(code=1, address=0x0) __insert_node_at() ,这会抛出EXC_BAD_ACCESS(code=1, address=0x0) 调用堆栈显示这是由第一个Button实例调用map[type] = this;

所以,这是我的多部分问题:

  1. 为什么编译顺序会导致这种情况发生?
  2. 有没有办法实现这种intButton*映射是由编译秩序的影响?
  3. 如果是这样,它是什么?

理想情况下,我仍然希望将所有与ControllerButton相关的代码放在单独的控制器。*文件中。


看起来这与以下问题有些相关(但不完全相同):

  1. std :: map :: insert(...)中的分段错误
  2. std :: map中的[]运算符给我分段错误

当您有多个全局构造函数时,它们的执行顺序是未定义的。 如果在map对象之前实例化controller对象,则控制器将无法访问该映射,从而导致段错误。 但是,如果首先实例化map对象,那么控制器可以很好地访问它。

在这种情况下,它似乎是它们在命令行中出现的顺序。 这就是为什么把你的main.cpp放在第一位导致段错误的原因 - controller对象首先被实例化了。

我建议在main移动实例化,因为这样你就可以精确控制对象的实例化方式和顺序。

静态变量的初始化顺序未定义,因此它取决于您的特定设置,包括编译器,链接器和链接顺序。

初始化函数技巧

您可以使用初始化函数技巧来确保在需要时初始化某些内容。 我知道这在Windows上使用Microsoft的C ++编译器是有效的(并且对使用Linux的g ++进行了一些测试,见下文)。

初始化函数

第一步是将地图作为静态变量移动到一个函数中,并始终通过此函数访问地图。

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

用法

首次调用buttonMap()函数时会创建映射。 如果您通过该功能访问地图,那么您可以确定它将被创建。

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

关键部分是全局变量的初始化:用引号替换它并从保存变量的函数初始化它。

buttonmap_t& map = buttonMap();

说明

使用此设置初始化顺序无关紧要,因为对函数的第一次调用将执行初始化,并且每次调用之后将使用初始化实例。

注意:此技巧适用于全局变量,因为初始化阶段是在单个线程上完成的。 即使您不知道初始化的确切顺序,也可以确保它将按顺序发生。

测试

我在家用电脑上使用g ++在Linux上进行了测试,它似乎有效:

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

最终计划:

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

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

使用“首次使用时构造”的习语,我做了以下修改:


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


这适用于编译顺序,并且不需要对“controller.h”或“main.cpp”进行任何更改。

暂无
暂无

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

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