[英]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.