Page 1 of 1

chHeapAlloc with polymorphic classes

Posted: Thu Jan 20, 2022 2:09 pm
by elasticdoor
Hello,

I am writing a program using the Arduino environment and the ChRt library https://github.com/greiman/ChRt, developing for the Teensy MicroMod.

As you may know, the Arduino environment allows for programming in C++, so this is the language that I'm using. I am implementing a dynamic list of objects derived from a common base class. I have a helper function to append items to the list. The base class features a pure virtual function that must be overridden by all subclasses. The code looks something like this:

Code: Select all

class Base {
public:
  Base *next = NULL;
  Base *prev = NULL;
  virtual void doWork() = 0;
};

class Child1 : public Base {
public:
  void doWork() { /* Child1 version of doWork() */ };
};

class Child2 : public Base {
public:
  void doWork() { /* Child2 version of doWork() */ };
};

Base *head = NULL;
Base *tail = NULL;

void append(Base *p) {
  if (head == NULL) {
    head = p;
    tail = p;
  }
  else {
    tail->next = p;
    p->prev = tail;
    tail = p;
  }
}

void main() {
  Child1 *c = (Child1 *)chHeapAlloc(NULL, sizeof(Child1));
  append(c);
  head->doWork(); /*Segmentation fault*/
}


As I mentioned in the code comment, calling the doWork function on an element of the list (here called on "head" for simplicity) gives a segmentation fault. The interesting thing is that if I skip explicitly instantiating c and instead I do

Code: Select all

append(new Child1());


the program runs no problem, the Child1 version of doWork is correctly called. So I have the suspicion that somehow, chHeapAlloc breaks polymorphism in some way. I could also be way off and any C++ advice would be welcome :)

Thank you all in advance for your help

Re: chHeapAlloc with polymorphic classes

Posted: Thu Jan 20, 2022 2:33 pm
by Giovanni
Hi,

That function behaves just like malloc(), I doubt it causes polymorphism to break. You need to look elsewhere.

If in doubt then try to replace chHeapAlloc() with the pool allocator, make a pool of your objects and allocate from there.

Giovanni

Re: chHeapAlloc with polymorphic classes

Posted: Thu Jan 20, 2022 3:25 pm
by elasticdoor
Thank you Giovanni.

For anybody who feels like taking a closer look at the code, I think it's relevant to specify that the Base pointers that are being initialized are actually contained into a container class, as follows

Code: Select all

class Base {
public:
  Base *next = NULL;
  Base *prev = NULL;
  virtual void doWork() = 0;
};

class Child1 : public Base {
public:
  void doWork() { /* Child1 version of doWork() */ };
};

class Child2 : public Base {
public:
  void doWork() { /* Child2 version of doWork() */ };
};

class Container {
  Base *head = NULL;
  Base *tail = NULL;
  void append(Base *p);
};

void Container::append(Base *p) {
  if (head == NULL) {
    head = p;
    tail = p;
  }
  else {
    tail->next = p;
    p->prev = tail;
    tail = p;
  }
}

void main() {
  Container *cont = new Container();
  Child1 *c = (Child1 *)chHeapAlloc(NULL, sizeof(Child1));
  cont->append(c);
  head->doWork(); /*Segmentation fault*/
}

Re: chHeapAlloc with polymorphic classes

Posted: Thu Jan 20, 2022 3:39 pm
by elasticdoor
Also, yes, you were absolutely right. I tried substituting chHeapAlloc with malloc and the problem is the same .

Re: chHeapAlloc with polymorphic classes

Posted: Thu Jan 20, 2022 4:13 pm
by elasticdoor
Final update:

As I discovered, neither chHeapAlloc nor malloc (which behave in the same way) preserve polymorphism, because they are just raw memory allocators and they do not actually properly initialize the object.

I solved my problem by allocating memory using chHeapAlloc and subsequently using placement new. The difference between placement new and "regular" new is that placement new skips the memory allocation step that "regular" new does. So I can safely allocate memory using chibiOS memory management functions and still have a properly initialized object. Code below:

Code: Select all

#include <new>

class Base {
public:
  Base *next = NULL;
  Base *prev = NULL;
  virtual void doWork() = 0;
};

class Child1 : public Base {
public:
  void doWork() { /* Child1 version of doWork() */ };
};

class Child2 : public Base {
public:
  void doWork() { /* Child2 version of doWork() */ };
};

class Container {
  Base *head = NULL;
  Base *tail = NULL;
  void append(Base *p);
};

void Container::append(Base *p) {
  if (head == NULL) {
    head = p;
    tail = p;
  }
  else {
    tail->next = p;
    p->prev = tail;
    tail = p;
  }
}

void main() {
  Container *cont = new Container();
  Child1 *c = (Child1 *)chHeapAlloc(NULL, sizeof(Child1));
  Child1 *child = new(c) Child1();
  cont->append(child);
  head->doWork(); /* Works properly*/
}