<<
>>

Sometimes dynamic memory allocation is needed. While often allocating memory statically leads to a more robust system, it’s not always possible within the contraints of the hardware.

In microcontrollers with a simple memory map of only one RAM area, the heap is just the part of the RAM that is not used by anything else in the linker script. So we can just add this after the last section that is put into RAM (.bss in this case):

+    __heap_start = .;
+    __heap_end = ORIGIN(RAM) + LENGTH(RAM);
+
+    /* Don't place any sections that are actually used on target system after the heap without adjusting _heap_end above */

In a sense that‘s all that needs to interact closely with the toolchain. Using __head_start and __heap_end you can implement you own allocator. If you call your allocation function malloc gcc does some optimizations based on builtin assumsations about functions from the C standard, so it might optimize out calls when you don‘t expect.

As i don‘t usually use dynamic memory allocation i tend to resort to use the malloc implementation from newlib in it’s nano configuration that is shipped with the toolchain.

To enable the nano configuration of the libc we need to pass -specs=nano.specs in the final link command. Newlib expects a traditional unix like sbrk function for heap size management.

/* needed for malloc in newlib */

extern "C" void* _sbrk(int increment);
void* __attribute__((weak)) _sbrk (int increment) {
    extern char __heap_start;
    extern char __heap_end;

    static void* __heap_top = &__heap_start;

    void *new_heap_top;
    if (__builtin_add_overflow((intptr_t)__heap_top, increment, (intptr_t*)&new_heap_top)) {
        return (void*)-1;
    }
    if (!(&__heap_start <= new_heap_top && new_heap_top <= &__heap_end)) {
        return (void*)-1;
    }

    void* old_top = __heap_top;
    __heap_top = new_heap_top;

    return old_top;
}

_sbrk is a fairly simple wrapper around a static variable that keeps track of the part of the heap already handed out to the malloc implementation, while checking that it stays inside of the heap. It is careful about integer overflows and uses the compiler intrinsic __builtin_add_overflow (needs a recent gcc to work).

With this bit of support code the newlib malloc should be usable. Or an other simple malloc implementation that uses sbrk.

With an working malloc implementation the new operator also works. Because we‘re using c++ without exceptions remember that it spells as new(std::nothrow) and needs #include <new>

The support code is included here

more in this series:
7. STM32 PLL and Crystal Oscillator
8. Interrupts
Enabling C/C++ features »
« STM32 PLL and Crystal Oscillator