We’re Officially Live β€” Lifetime Founding Membership Available for a Limited Time
Free preview

The Memory Map

Why code lives at 0x08000000 and variables at 0x20000000: the Cortex-M address layout of flash, SRAM, and peripherals, and how stack and heap share the SRAM with no MMU to referee.

20 min read

In this lesson:

This is the MCU-specific view of the layout sketched in the C stack-vs-heap lesson.


What a memory map is

The CPU has one flat address space (4 GB on a 32-bit Cortex-M: 0x0000_0000-0xFFFF_FFFF). The memory map is the fixed assignment of which address range hits what: this band is flash, that band is SRAM, that band is peripheral registers. It's defined by the chip designer and burned into the silicon, your linker script must match it or nothing runs.


The canonical Cortex-M map

ARM standardizes the broad regions; vendors place their flash/SRAM/peripherals within them. A typical STM32 layout:

0xFFFF_FFFF β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚ System (PPB): NVIC, SysTick,β”‚  0xE000_0000  β€” core peripherals
            β”‚ SCB β€” the "private periph"  β”‚
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
            β”‚ Peripherals: GPIO, UART,    β”‚  0x4000_0000  β€” memory-mapped registers
            β”‚ timers, ADC, ... (APB/AHB)  β”‚
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
            β”‚ SRAM (data, stack, heap)    β”‚  0x2000_0000
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
            β”‚ Flash (program + const)     β”‚  0x0800_0000  β€” STM32 flash base
            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
            β”‚ Boot alias (β†’ flash/sys/RAM β”‚  0x0000_0000  β€” what the CPU sees at reset
            β”‚  per BOOT pins)             β”‚
0x0000_0000 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The four bands you care about:

Region Typical base Holds
Flash 0x0800_0000 program code, const, the initial vector table
SRAM 0x2000_0000 .data, .bss, heap, stack
Peripherals 0x4000_0000 memory-mapped registers (GPIO, timers, …)
System (PPB) 0xE000_0000 NVIC, SysTick, SCB, inside the core

(Exact bases are vendor-specific, always check your chip's reference manual. The bands are ARM-standard; the numbers within differ.)


How SRAM is divided

The SRAM holds several things that the linker lays out from the bottom up, with the stack at the very top growing down toward them:

high addr β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  ← top of SRAM (initial stack pointer)
          β”‚        stack         β”‚  grows DOWN ↓ (locals, call frames)
          β”‚          β”‚           β”‚
          β”‚          β–Ό           β”‚
          β”‚   (free SRAM gap)    β”‚  ← the danger zone: stack and heap
          β”‚          β–²           β”‚     grow toward each other here
          β”‚          β”‚           β”‚
          β”‚        heap          β”‚  grows UP ↑ (malloc, if used)
          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
          β”‚   .bss (zero-init)   β”‚  uninitialized globals/statics
          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
          β”‚  .data (init values) β”‚  initialized globals/statics
low addr  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  ← 0x2000_0000

The collision danger: no MMU to referee

On a desktop, the MMU and guard pages catch a stack that grows too far. A typical Cortex-M MCU has no MMU, nothing referees the gap. If the stack grows down far enough to meet the heap (or .bss) growing up, they silently overwrite each other. The symptom is corrupted variables or a wild crash far from the actual cause (the stack-overflow point from the C module, made concrete here).

Defenses: size the stack from worst-case call depth + ISR nesting, avoid deep recursion and large local arrays, paint SRAM with a known pattern at boot to measure the stack high-water mark, and optionally use the MPU (memory protection unit) to mark a guard region that faults on access.


The boot alias at address 0, and unmapped memory

At reset the Cortex-M fetches two words from address 0x0000_0000: the initial stack pointer and the initial program counter (reset handler). On many MCUs 0x0000_0000 is an alias that the BOOT configuration pins map to flash, system memory (the built-in bootloader), or SRAM, which is how you select "boot from flash" vs "boot from the ROM bootloader." Normally it aliases flash, so the vector table at 0x0800_0000 is what the core reads.

Accessing an address in an unmapped gap (no memory or peripheral there) triggers a BusFault/HardFault, the MCU's version of a segfault. A null-pointer write (*(int*)0 = 1) often lands in the flash/alias region and faults or silently misbehaves, which is why null derefs are dangerous but not always a clean crash on bare metal.


Gotchas


TL;DR

This is just the start

Sign up free to track progress on this lesson, get coding problems with the Run button, and unlock the full interview Q&A.

More in Embedded Systems Fundamentals

What Is an Embedded System?Inside an MCU
Registers & Memory-Mapped I/O
GPIO: Digital I/O
Interrupts & ISRs
Timers & Counters