C++ placement new alignment of classes (on a SAMD21 microcontroller)

144 views Asked by At

I am working on an application which is running on a SAMD21 microcontroller. For those unfamiliar with the SAMD21, it contains an ARM Cortex-M0+ processor. The specific model I am using has 32 kB of RAM. My application is running up to the limits of that 32 kB, so I've been working on optimizing the code for memory usage.

One optimization I've been working on is reducing heap fragmentation. For example, I may have a scenario such as the following:

A *my_obj = new A();
B *my_second_obj = new B();
delete A;
C *my_third_obj = new C();

In the above example, when instantiating my_third_obj, the memory allocator may attempt to place it in the empty location that was initially being used by my_obj. However, if my_third_obj doesn't fit in that spot, then the memory allocator will simply allocate some more space at the top of the heap and move the heap pointer. This will leave a "hole" where my_obj was located (which may be filled later by other objects), but this creates heap fragmentation.

In my specific application, I've determined that I will ever only need one active instance of classes A, B, or C at any point in time. Because of this, I was thinking about creating a block of memory which holds the current instance of any of those classes, and simply making the memory block as big as the largest class so that it can hold any of the classes. This would reduce heap fragmentation since there would always be a specific place in memory where I would be allocating these specific classes.

Here's a simple example of what I am thinking:

uint32_t max_size = sizeof(A);
max_size = (sizeof(B) > max_size) ? sizeof(B) : max_size;
max_size = (sizeof(C) > max_size) ? sizeof(C) : max_size;
uint8_t *buffer = new uint8_t[max_size];

//Some time later in the program...
C *my_obj = new(buffer) C();

//Some time later in the program...
my_obj->~C();
my_obj = NULL;
memset(buffer, 0, sizeof(max_size));
B *my_other_obj = new(buffer) B();

I've never really used placement new in previous code that I've written, but I think it would be useful in my current circumstance. My main question here is: given the example that I've laid out, do I need to alter the code in any way to handle alignment issues? Classes A, B, and C all have different member variables and different sizes. Will this code just "work", or do I need to do anything special to handle memory alignment?

Thanks!

2

There are 2 answers

1
KamilCuk On BEST ANSWER

do I need to alter the code in any way to handle alignment issues?

Yes.

do I need to do anything special to handle memory alignment?

Yes.

uint8_t conceptually represents an unsigned integer with 8 bits. Use char or unsigned char to represent 1 byte.

Anyway, use operator new with size and alignment:

auto maxsize = max_of_3(sizeof(A), sizeof(B), sizeof(C)),
auto neededalign = std::align_val_t(max_of_3(alignof(A), alignof(B), alignof(C));
void *buffer = operator new(maxsize, neededalign);

or statically:

std::aligned_storage<
    max_of_3(sizeof(A), sizeof(B), sizeof(C)),
    max_of_3(alignof(A), alignof(B), alignof(C))> buffer;
A *stuff = new(buffer.data) A;
1
Serge Ballesta On

A buffer obtained from malloc is guaranteed to be correctly aligned for any basic type, but I am unsure whether a pointer obtained from new is, so I would prefer:

uint8_t *buffer = malloc(max_size);   // delete it later with free

But you could even get rid of any dynamic allocation by building a custom buffer using alignas:

// only required for C++11, starting from C++14, std::max is constepr
constexpr size_t max3(size_t i, size_t j, size_t k) {
    uint32_t max_size = i;
    max_size = (i > j) ? i : j;
    max_size = (k > max_size) ? k : max_size;
    return max_size;
}

// declare a custom struct with required size and alignment
struct alignas(max3(alignof(A), alignof(B), alignof(C))) Buffer {
    char buffer[max3(alignof(A), alignof(B), alignof(C))];
};

// build a statically allocated buffer of correct size and alignment
Buffer buffer;

From that point on, you can safely inplace construct an object in buffer and of course explicitely destroy it before re-using the memory.