Memory layout
The linker sorts a program into sections. Code (.text) and constants (.rodata) stay in Flash. Initialised variables (.data) keep their starting values in Flash and are copied to RAM at startup; zero-initialised ones (.bss) are cleared. The heap and stack share the RAM that is left.
Sections
The compiler puts each object into a named section, and the linker script decides where each section goes. Each line of the example is commented with its section. Functions go into .text and const data into .rodata, both in Flash. A global or static variable with a non-zero starting value goes into .data. One without a starting value, or starting at zero, goes into .bss.
#include <stdint.h>const uint8_t lookup[4] = {1, 2, 4, 8}; /* .rodata: Flash only */uint32_t boot_count = 1; /* .data: value in Flash, copied to RAM */uint32_t errors; /* .bss: RAM, zeroed at startup */uint32_t work(uint32_t x) { /* the code itself: .text, Flash */ static uint32_t calls; /* .bss as well */ uint32_t local = x * lookup[x & 3u]; /* stack */ calls++; boot_count++; errors += (local == 0u); return local + calls;}
Startup fills RAM
RAM holds nothing useful after reset, so before calling main the startup code copies the starting values of .data from Flash into RAM and fills .bss with zeros. That is why a global without an initialiser is guaranteed to start at 0, and why every initialised global costs both Flash and RAM. Local variables are not in any of these sections: they live on the stack, created on each call.
Heap and stack
The rest of the RAM is shared. On many MCUs the stack starts at the top and grows down, and the heap, used by malloc, starts after .bss and grows up. Nothing stops them meeting: a stack that grows too deep overwrites the heap or .bss silently, unless a memory protection unit or a stack limit check is set up.