C++:分段错误 - 通过 std::vector<> 使用虚函数和抽象 class 调用函数

C++: Segmentation fault - Calling function through std::vector<> using a virtual function and abstract class

对于某些人来说,这可能是一个明显的错误,但我似乎无法找出为什么会出现此分段错误。

我知道当访问我的程序不应该访问的地址时会发生分段错误。

首先,我有一个名为“UiObject”的基础-class:

class UiObject {
  private:

  public:
    UiObject();
    virtual void render(char * imageBuffer) = 0;
};

然后,我从 UiObject class 派生多个其他 classes: 例如,ProgressBar class:

class ProgressBar : public UiObject {
    private:
        int x,y; // Position der ProgressBar auf dem Bildschirm (oben links) (Pixeln)
        int width,height; // Breite und Höhe in Pixeln
    public:
        int maxProgress; // Maximal zu erreichner Fortschritt
        long progress; // Fortschritt aktuell
        ProgressBar(); // Constructor
        void render(char * framebuffer); // Rendermethode
};

现在,这些对象由 UiManager 管理和呈现。 函数 renderDisplay() 是使用 std::thread:

创建的线程
void UiManager::renderDisplays() {
  printf("[RENDER] Render thread has been started\n[RENDER] (%x)\n", this);
  while(!this->shouldStop) {
    // Removed code that manages the timing of this function (running approximately 60FPS)
  
    // Rendering:

    for(Display d: displayArray) {
      char * framebuffer = d.getFrameBuffer();
      printf("Framebuffer: %x\n", framebuffer);
      printf("uiArray: %x\n", uiArray);
      printf("&uiArray: %x\n", &uiArray);
      for(UiObject* pObject : uiArray) {
        printf("[RENDER] (%x) Rendering Object...", pObject);
        pObject->render(framebuffer);
      } 
      d.renderDisplay();
    }
  }
}

如您所见,对于每个添加的显示器,我都在检索帧缓冲区。我正在打开 /dev/fbX 文件,这样我就可以直接在屏幕上绘图了。这一切都很好。 d.getFramebuffer() returns 一个 char * 指针,这样我就可以操作像素,然后将其传递到任何 UiObject 的 render(char * framebuffer) 函数中。

但是,这个函数调用正在创建一个段错误,我不明白为什么。 有时它会工作并且一直运行到线程应该停止为止,有时它会在第一次渲染函数调用后立即崩溃。

我使用以下方法将 UiObject 添加到 UiManager:

 ProgressBar pBar;
 pBar.maxProgress = 60*15;
 pBar.progress = 1;

 uiArray.push_back(&pBar);

UiManager 将此作为其 class 定义:

  class UiManager {
  private:
    char ttyfd; // TTY File Descriptor.
    std::thread uiManagerThread;
    std::chrono::system_clock::time_point timeA;
    std::chrono::system_clock::time_point timeB;
    std::chrono::duration<double, std::milli> work_time;

  public:
    std::vector<Display> displayArray;
    std::vector<UiObject*> uiArray;
    bool shouldStop;
    bool displayFPS;

    UiManager();

    void stop();

    void renderDisplays();

    void addDisplay(Display d);

    void startThread();

    void stopThread();
};

现在我想知道这是为什么。

根据 cppreference https://en.cppreference.com/w/cpp/language/abstract_class 和他们关于抽象 classes 的文档,我不允许有 =0;在我的 UiObject class 中。 如果我删除它,我将在链接期间收到编译器警告消息“未定义对 UiObject 的 vtable 的引用”。

我做错了什么?

我怀疑 std::vector 和 for 循环在 UiManager 中不能正常工作。

我在 debian 上使用 g++ 作为我的编译器。

我的控制台输出:

[RENDER] Render thread has been started
[RENDER] (6ea609f0)
Framebuffer: e09f0010
uiArray: e09eed20
&uiArray: 6ea60a30
./run.sh: Zeile 21: 25018 Speicherzugriffsfehler  ./a.out

它甚至没有跳入 ProgressBar 渲染例程。我也注释掉了render-function的内容,所以我只能怀疑具体的函数调用。

= 0 抽象基础 class 是正确的。


然而,

 ProgressBar pBar;
 //...
 uiArray.push_back(&pBar);

不是。

pBar 将在声明它的范围离开后被销毁,然后在 renderDisplays 中稍后尝试取消引用悬空指针是未定义的行为。

您需要使用 new 创建对象,或者最好将 std::unique_ptr<UiObject> 存储在向量中并使用 std::make_unique<ProgressBar>().

创建对象

此外,UiObject 需要一个虚拟析构函数:virtual ~UiObject() = default; 否则通过基础 class 指针销毁对象将导致未定义的行为。


那么两个线程中对uiArray的访问似乎也没有同步。

访问 uiArray 进行修改并在没有同步的情况下在两个线程中读取是数据竞争和未定义的行为。

在两个线程中访问uiArray时需要添加互斥量并加锁。

I add the UiObjects to the UiManager using this:

您似乎是在堆栈上分配这些 ProgressBar 对象。我没有你所有的代码,但我想知道这是不是在另一个函数中完成的,然后在使用它们时堆栈不存在。这可以解释间歇性错误,因为有时该函数调用的堆栈仍然存在。要解决此问题,我会在堆上分配 ProgressBar 个对象:

 ProgressBar* pBar = new ProgressBar();
 pBar->maxProgress = 60*15;
 pBar->progress = 1;

 uiArray->push_back(pBar);