on 2016-04-10 in stm32-from-scratch
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