Visual walkthrough — Startup code — vector table, reset handler, stack initialization
Before Step 1, three plain-word anchors we will reuse everywhere:
Related building blocks live in Linker scripts and memory sections (.text .data .bss), ARM Cortex-M exception and interrupt model, and Stack vs Heap memory layout.
Step 1 — The machine at power-on: two memories, one confused CPU
WHAT. At the instant power stabilises, we have two separate memories and a CPU that has done nothing yet.
- Flash (non-volatile, keeps its contents with power off): holds your program image — the machine code, and the initial values of your variables. It typically starts at address
0x0800_0000(or is aliased to0x0000_0000). - RAM (volatile, contents undefined at power-up): where variables will live and change. Starts at
0x2000_0000.
WHY this picture first. Every later step is a copy or a clear moving data between these two boxes, or a pointer aimed into one of them. If you hold the two-box picture, nothing after this surprises you.
PICTURE. Flash on the left is full and orderly; RAM on the right is filled with question marks — genuinely unknown values.

Step 2 — The vector table: the only two words hardware reads by itself
WHAT. At the very bottom of the program image sits a small array called the vector table. The CPU, entirely in hardware, reads exactly its first two words:
Reading term by term:
- — the 32-bit number stored at offset
0x00. The hardware copies it into SP. It is not code; it is an address, the intended top of the stack. - — the number at offset
0x04. The hardware copies it into PC. This one is an address of code — the Reset Handler.
WHY these two, in this order. The stack must be valid before any instruction runs, because the very machinery that responds to interrupts pushes onto the stack. So the chip designers load SP first, from the first word — no code has run yet, so this must be pure hardware. Then PC, so execution has somewhere to go.
PICTURE. Two amber arrows leap out of the table's first two rungs into the CPU's SP and PC boxes.

Step 3 — Loading SP: the stack-top, and why it points at RAM's ceiling
WHAT. The number in word 0 is _estack — supplied by the linker as (highest RAM address) + 1. For a 20 KB RAM at 0x2000_0000, that is:
- — the first RAM address (the floor).
- — 20 KB in hex ().
- — one past the last valid rung: the ceiling+1.
WHY the top, not the bottom? The stack is full-descending: SP points at the last item written, and a push first subtracts 4 from SP, then writes. So SP must start high and walk down. Meanwhile the heap (if any) grows up from the low end. Starting them at opposite ends means they only collide in one predictable way. (More in Stack vs Heap memory layout.)
PICTURE. RAM as a vertical bar; SP sits at the top edge; a dashed amber arrow shows the direction of future growth pointing downward.

Step 4 — Loading PC and jumping into the Reset Handler
WHAT. Word 1 goes into PC, so the CPU's next fetched instruction is the first instruction of Reset_Handler. Execution begins here. There is no main() yet, no globals yet — just SP set and PC aimed.
WHY a handler and not main directly. main assumes globals already hold their values and .bss is zero. None of that is true yet. The Reset Handler is the minimum glue that manufactures those facts (Steps 5–6) before handing control to main. Jumping straight to main skips the manufacturing — initialised globals would read Flash-time garbage. (Compare The C runtime and crt0.)
PICTURE. The PC arrow now lands on the first rung of the Reset Handler's code inside Flash; a caption lists its to-do list.

Step 5 — Copy .data: give initialised globals their values
WHAT. For every initialised global (int g = 42;), the value 42 was baked into Flash at build time, but the variable must live in writable RAM. So the handler copies a block Flash→RAM. The linker hands us three markers:
_sidata— where the init image sits in Flash (source start)._sdata,_edata— start and end of.datain RAM (destination).
The copy for the word at RAM offset :
- — how far into the RAM
.datablock we are (0 for the first word, 4 for the next…). - — the same distance into the Flash image. n-th RAM word ← n-th Flash word.
In code it is a two-pointer march: while (dst < &_edata) *dst++ = *src++;
WHY. Flash cannot be written by normal stores at run time, and its values must survive power cycles. RAM can change (a global you assign to) but starts as garbage. Copying reconciles both: correct initial value and writability.
PICTURE. Two cursors walk in lockstep — src down the Flash image, dst down RAM — arrows carrying 42 across.

Step 6 — Zero .bss: turn undefined RAM into guaranteed zero
WHAT. Uninitialised globals (int z;, int counter;) form the .bss region. C guarantees they read as 0, but no zeros are stored in Flash (why waste space storing zeros?). The handler simply writes 0 across the range:
_sbss,_ebss— start and end of the.bssblock in RAM (from the linker).- The loop overwrites the power-on garbage with clean zeros, one word at a time.
WHY. Step 1 showed RAM starts as noise. Without this loop, int counter; might begin at some random value; counter++ produces nonsense. Zeroing converts undefined into the C-promised 0.
PICTURE. The .bss slice of RAM flips from question marks to a solid run of 0s, a broom-cursor sweeping through.

Step 7 — Constructors, then jump to main()
WHAT. With .data correct and .bss zeroed, the C environment exists. Two last acts:
__libc_init_array()runs C++ static/global constructors (harmless no-op in pure C).- Branch-with-link into
main(). This is a normal function call: the return address is pushed — the first real use of the stack.
WHY the loop-forever after main. There is no operating system to return to on bare metal. If main ever returns, while(1){} traps the CPU deliberately rather than executing whatever garbage sits past the call.
PICTURE. main's return address lands at 0x2000_4FFC — exactly _estack - 4 — confirming Step 3's off-by-one.

Step 8 — The degenerate cases (never leave the reader stranded)
WHAT / WHY, case by case:
- Empty
.data(no initialised globals):_sdata == _edata. Thewhile (dst < &_edata)test is false immediately — zero iterations, correctly copies nothing. No special-casing needed. - Empty
.bss(no uninitialised globals):_sbss == _ebss, loop runs zero times. Correct. - Zero-size stack usage before RAM ready: impossible to avoid — that is why SP is loaded by hardware in Step 2, before any code. The ordering is the safeguard.
- VTOR relocation (bootloader present): the table need not sit at
0x0000_0000; a register calledVTORpoints the hardware at wherever the table actually lives. Everything above still holds — only the offset the hardware reads from changes. See Bootloaders and VTOR relocation. - Stack overflow: with no MMU guard page, the stack silently grows past its region into
.bss/heap and corrupts them. Detection needs a linker_Min_Stack_Sizegap, theMSPLIMregister (Cortex-M33), or a fill-pattern watermark — the hardware will not warn you.
PICTURE. Left panel: an empty range where start == end (loop skips). Right panel: the stack arrow crashing through the floor into .bss, an amber warning where the collision happens.

The one-picture summary
Everything on one canvas: hardware loads SP and PC from the table's two words → Reset Handler copies .data Flash→RAM → zeros .bss → runs constructors → calls main with a working full-descending stack.

Recall Feynman retelling — say it to a friend
The chip wakes up dumb. Two memories exist: Flash (your program, remembers everything, read-only at runtime) and RAM (scratch space, starts as pure garbage). The CPU's first act, done in silicon with zero code, is to read two numbers off the front of your program: the first becomes the stack pointer (a bookmark to the top of scratch space), the second becomes the program counter (go run this code). That code is the Reset Handler. It does three chores: copy the starting values of your int x = 42; globals from Flash into RAM; scrub the "should-be-zero" globals so they're actually zero (not luck-of-the-draw noise); run any constructors. Only then does it call main. The stack lives at the ceiling of RAM and grows down, so the first function call bookmarks itself one word below the very top. If main ever finishes, the code spins forever, because on bare metal there's nowhere to go home to.
Recall
First two words of the vector table go where? ::: Word 0 → SP (initial stack top), Word 1 → PC (Reset Handler address).
Why copy .data from Flash to RAM? ::: Initial values are baked in read-only Flash; the variable must live in writable RAM, so we copy value + get writability.
Why zero .bss explicitly? ::: RAM is undefined at power-up; the loop converts garbage into the C-guaranteed 0.
Where does the first push land for _estack = 0x2000_5000? ::: At 0x2000_4FFC = _estack - 4 (full-descending stack).
Parent: Back to 5.5.16 · Prereqs: Volatile, memory-mapped registers and hardware init, The C runtime and crt0.