簡體   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