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

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

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

为了跟踪这种关联,我使用了一个std :: map< int,Controller :: Button *>,我将其输入到buttonmap_t.

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

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正在调用__insert_node_at(),这会抛出EXC_BAD_ACCESS(代码= 1,地址= 0x0).调用堆栈显示这是由第一个Button实例调用map [type] = this;触发的.

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

>为什么编译顺序会导致这种情况发生?
>有没有办法实现这种不受影响的Button *映射的int
编译顺序?
>如果是的话,它是什么?

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

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

> Segmentation fault in std::map::insert(…)
> [] operator in std::map is giving me segmentation fault

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

初始化函数技巧

您可以使用初始化函数技巧来确保在需要时初始化某些内容.我知道这可以在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;
}

转载注明原文:c – 为什么编译顺序在使用std :: map :: insert()时有时会导致分段错误? - 代码日志