4

I am attempting to use C++ for AVR programming using gcc-avr. The main issue is that there is no libc++ available and the implementation does not define any of the new or delete operators. Similarly there is no header to include using placement new is not an option.

When attempting to allocate a new dynamic object I am tempted to just do this:

Class* p = reinterpret_cast<Class*>(malloc(sizeof(Class)));
p->Init();

where Init() manually initializes all internal variables. But is this safe or even possible?

I have read that object construction in C++ is somewhat complex but without new or delete how do I initialize a dynamically allocated object?


To expand on the above question.

Using standard g++ and placement new it is possible to subvert constructors in two ways, assuming that C++ uses the same straightforward ways of alligning memory as C (code example below).

  1. Using placement new to initialize any allocated memory.
  2. Initialize allocated memory directly using class methods.

Of course this only holds if the assumptions are true that:

  • Memory layout of an object is fixed at compile time.
  • Memory allocation is only concerned with class variables and observers normal C rules (allocated in order of declaration aligned to memory boundary).

If the above holds could I not just allocated memory using malloc and use a reinterpret_cast to convert to the correct class and initialize it manually? Of course this is both non-portable and hack-ish but the only other way I can see is to work around the problem and not use dynamically allocated memory at all.

Example:

Class A {
    int i;
    long l;
    public:
        A() : i(1), l(2) {}
        int get_i() { return i; }
        void set_i(int x) { i = x; }
        long get_l() { return l; }
        void set_l(long y) { l = y; }
};

Class B {
     /* Identical to Class A, except constructor. */
     public B() : i(3), l(4) {}
};

int main() {
     A* a = (A*) ::operator new(sizeof(A));
     B* b = (B*) ::operator new(sizeof(B));

     /* Allocating A using B's constructor. */
     a = (A*) new (a) B();

     cout << a->get_i() << endl; // prints 3
     cout << a->get_l() << endl; // prints 4

     /* Setting b directly without constructing */
     b->set_i(5);
     b->set_l(6);

     cout << b->get_i() << endl; // prints 5
     cout << b->get_l() << endl; // prints 6
Kenneth
  • 1,167
  • 1
  • 13
  • 26

2 Answers2

2

If your allegedly C++ compiler does not support operator new, you should be able to simply provide your own, either in the class or as a global definition. Here's a simple one from an article discussing operator new, slightly modified (and the same can be found in many other places, too):

void* operator new(size_t sz) {
    void* mem = malloc(sz);
    if (mem)
        return mem;
    else
        throw std::bad_alloc();
}


void operator delete(void* ptr) {
    free(ptr);
}

A longer discussion of operator new, in particular for class-specific definitions, can also be found here.

From the comments, it seems that given such a definition, your compiler then happily supports the standard object-on-heap creations like these:

auto a = std::make_shared<A>();
A *pa = new A{};

The problem with using Init methods as shown in the code snippet in your question is that it can be a pain to get that to work properly with inheritance, especially multiple or virtual inheritance, at least when something during object construction might throw. The C++ language has elaborate rules to make sure something useful and predictable happens in that situation with constructors; duplicating that with ordinary functions probably will get tricky rather fast.

Community
  • 1
  • 1
Christopher Creutzig
  • 8,656
  • 35
  • 45
  • Good explanation. I'll just add that there are lots more things that avr-g++ does not do besides new and delete. These include exceptions in any form and the entire libc++ library (class, functions, templates, etc.). It's summarized here: http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_cplusplus.html – Kenneth Apr 06 '15 at 11:43
1

Whether you can get away with your malloc()-reinterprete_cast<>-init() approach depends on whether you have virtual functions/inheritance. If there is nothing virtual in your class (it's a plain old datatype), your approach will work.

However, if there is anything virtual in it, your approach will fail miserably: In these cases, C++ adds a v-table to the data layout of your class which you cannot access directly without going very deep into undefined behavior. This v-table pointer is usually set when the constructor is run. Since you can't safely mimic the behavior of the constructor in this regard, you must actually call a constructor. That is, you must use placement-new at the very least.

Providing a classless operator new() as Christopher Creutzig suggests, is the easiest way to provide full C++ functionality. It is the function that is used internally by new expressions to provide the memory on which the constructors can be called to provide a fully initialized object.


One last point of assurance: as long as you do not use a variable length array at the end of a struct like this

typedef struct foo {
    size_t arraySize;
    int array[];
} foo;

the size of any class/struct is entirely a compile time constant.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106