[英]Why does compile order sometimes cause a segmentation fault when using 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*>
这是我typedef
到buttonmap_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;
。
所以,这是我的多部分问题:
int
到Button*
映射是由编译秩序的影响? 理想情况下,我仍然希望将所有与Controller
和Button
相关的代码放在单独的控制器。*文件中。
看起来这与以下问题有些相关(但不完全相同):
当您有多个全局构造函数时,它们的执行顺序是未定义的。 如果在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.