简体   繁体   中英

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

This may be a total obvious error for some, but I can't seem to find out why this segmentation fault happens.

I do understand that segmentation faults occur when accessing an address that my program should not access.

First, I have this base-class called "UiObject":

class UiObject {
  private:

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

Then, I am deriving multiple other classes from the UiObject class: For example, the 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
};

Now, these objects are managed and rendered by an UiManager. The function renderDisplay() is a thread created with 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();
    }
  }
}

As you can see, for every added Display I am retrieving the framebuffer. I'm opening the /dev/fbX files so i can draw directly onto the screen. This all works fine. d.getFramebuffer() returns a char * pointer so i can manipulate the pixels, which is then passed into the render(char * framebuffer) function of any UiObject.

However, this function call is creating a segfault, and I can't understand why. There are sometimes when it works and just runs until the thread should stop, and sometimes it crashes immidiately after the first render-function call.

I add the UiObjects to the UiManager using this:

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

 uiArray.push_back(&pBar);

The UiManager has this as its class-definition:

  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();
};

Now I'm wondering why this is.

According to the cppreference https://en.cppreference.com/w/cpp/language/abstract_class and their documentation about abstract classes, I'm not allowed to have the =0; in my UiObject class. If I remove it, I will get compiler warnings during linking with the message "undefined reference to vtable for UiObject".

What have I done wrong?

I suspect that the std::vector and the for-loop are not quite working in the UiManager.

Im using g++ on debian as my compiler.

My console output:

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

It also does not even jump into the ProgressBar render routine. I also commented out the contents of the render-function, therefore i can only suspect that specific function call.

= 0 for an abstract base class is correct.


However,

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

is not.

pBar will be destroyed once the scope in which it is declared is left and then trying to dereference the dangling pointer later in renderDisplays is undefined behavior.

You need to create the object with new , or better store std::unique_ptr<UiObject> in the vector and create objects with std::make_unique<ProgressBar>() .

Also, UiObject needs a virtual destructor: virtual ~UiObject() = default; Otherwise destroying objects through the base class pointer will cause undefined behavior.


Then there seems to be also no synchronization between the access to uiArray in the two threads.

Accessing uiArray for modification and read in two threads without synchronization is a data race and undefined behavior.

You need to add a mutex and lock it when accessing uiArray in both threads.

I add the UiObjects to the UiManager using this:

It looks like you're allocating these ProgressBar objects on the stack. I don't have all of your code, but I wonder if this is being done in another function and then the stack doesn't exist when they're being used. This would explain the intermittent error as sometimes the slack for that function call still exists. To fix this, I'd allocate the ProgressBar objects on the heap:

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

 uiArray->push_back(pBar);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM