Intuition The one core idea
A freshly-powered CPU knows nothing about your C program — it just reads a number to use as a stack, then reads an address and jumps there. Startup code is the tiny stretch of code sitting at that address whose only job is to build the world your C program assumes already exists: a working stack, globals holding their correct values, and everything else set to zero.
Before you can read the parent note, you need the vocabulary it speaks. This page builds every term and piece of notation from absolute zero — no prior knowledge of CPUs, memory, compilers or linkers assumed. Read top to bottom; each block leans on the one above it. Two words you'll meet early — reset handler and linker — get a one-line preview here and a full definition later:
Definition Two words you'll see before their full section
Reset handler : the very first piece of your code that runs after power-on. Its job is to build the C environment (copy and zero memory) and then call main(). Full treatment arrives once you have the vocabulary — for now, just read it as "the boot code we are writing."
Linker : the tool that, after compiling, decides which address every function and variable lives at and stitches the pieces into one program image. Defined properly in §7.
Definition Memory and addresses
Think of all the storage in a computer as a long shelf of tiny boxes , each holding one byte (a number from 0 to 255). Every box has a permanent number stamped on it — its address . "Address 0x20000000" just means "the box numbered 0x20000000".
The 0x prefix means the number is written in hexadecimal (base-16) — a compact way to write big addresses. 0x10 is sixteen, 0xFF is 255. Engineers use hex because memory addresses look tidy in it.
Definition The underscore in numbers is just a comma
Long hex addresses are hard to read, so we often write 0x2000_5000 instead of 0x20005000. The ==underscore _== inside a number is a digit separator — pure decoration to group digits, exactly like the comma in 1,000,000. It changes nothing about the value; 0x2000_5000 and 0x20005000 are the same number . C itself allows this grouping too.
Definition Word = 4 bytes = 32 bits
A word on these chips is 4 bytes = 32 bits — the natural chunk the CPU reads and writes in one go. Because one word spans 4 boxes, whole words sit at addresses 0x00, 0x04, 0x08, 0x0C, ... (jumping by 4). Every important number here — an address, the stack-top, an initial value — fits in exactly one word.
Figure s01 below draws this wall: five boxes in a row, each labelled with its address 0x00, 0x04, 0x08, …. The red note reminds you that addresses jump by 4 because one word spans 4 boxes. Keep this picture in mind: every later step is "go to that box, read or write a word."
Intuition Why numbered boxes matter here
The whole startup story is "the CPU reads box #0, then box #4, then copies boxes from one region to another." If you picture memory as this numbered wall, every later step is just "go to that box, read/write a word."
Related layout ideas live in Stack vs Heap memory layout .
A value is aligned when its address is a multiple of its size: a 4-byte word must start at an address divisible by 4 (0x00, 0x04, 0x08, …), never at 0x03. Cortex-M hardware requires word accesses to be aligned — a misaligned word read can fault. This is why every table slot and every stack push moves in steps of 4, and why the initial SP must be a multiple of 4 (in fact 8 for the ABI). Alignment is not a style choice; it is what keeps the reads legal.
Definition Byte order (endianness)
A 32-bit number spans four boxes, so which byte goes in the lowest-addressed box? Cortex-M is little-endian : the least-significant byte sits at the lowest address. So the number 0x2000_5000 stored at 0x00 puts 0x00 in box 0x00, 0x50 in box 0x01, 0x00 in box 0x02, 0x20 in box 0x03.
Why you must know this: when the reset handler copies .data word by word (4 bytes at a time), byte order never matters — a whole word is moved intact. But if you ever inspect memory byte by byte in a debugger, 42 (0x0000002A) shows up as 2A 00 00 00, which looks "backwards." That surprise is endianness, not a bug.
Flash : memory that remembers when power is off . Your program's machine code and the initial values of variables are burned here. It is read-only at runtime (slow/awkward to write).
RAM : memory that is fast but forgetful — its contents are garbage/undefined at power-up and vanish when power is cut. Variables that change at runtime live here.
Figure s02 contrasts them as two boxes: Flash (black) holds your code and the init value 42; RAM (black) is where variable g actually lives at runtime but starts as garbage. The red arrow between them is the single most important action on this whole page — the copy of 42 from Flash into RAM. Everything the reset handler does is captured by that one red arrow plus a wipe.
Intuition Why the split creates work
A line like int g = 42; needs g to live in RAM (so it can change) but its starting value 42 can only survive power-off in Flash. So the number 42 sits in Flash and something must copy it into RAM before main() runs. That "something" is the reset handler (previewed above — the boot code we are writing). This copy is the reason startup code exists at all.
Definition CPU and Program Counter (PC)
The CPU is the worker that reads instructions and does them. It keeps one special number, the Program Counter (PC) : the address of the next instruction to run. Each step: read the instruction at PC, do it, advance PC. "Jumping" means writing a new address into PC.
The PC is a finger pointing at the wall of boxes , sliding right as instructions run. At reset the chip designers physically wire the finger to a fixed starting spot — that is the entire meaning of "the CPU begins executing from a hardware-defined address."
More on the Cortex-M machine model: ARM Cortex-M exception and interrupt model .
Definition Stack and Stack Pointer
The stack is a scratch region of RAM used like a stack of plates: when a function is called, its return address and local variables are pushed on top; when it finishes, they are popped off. The Stack Pointer (SP) is a number holding the address of the current top plate. On Cortex-M the main one is called MSP (Main Stack Pointer).
Figure s03 stands memory up vertically. The top marker is _estack; below it sit two pushed items (a return address and a local variable). The red arrow on the left is the SP, always pointing at the last item written ; the red arrow on the right shows the growth direction — downward , because each push subtracts 4 first, then writes. Read the figure top-to-bottom as "high addresses at the top, stack eating downward toward low addresses."
Definition Full-descending stack
On Cortex-M the stack is full-descending : SP points at the last item written , and a push first subtracts 4 from SP, then writes . So the stack grows downward (toward smaller addresses). This is why the starting SP is set to the top address and the first push lands just below it. Note that because pushes move in steps of 4 (or 8 for the ABI), a properly-aligned initial SP stays aligned forever — see the alignment definition in §1.
Intuition Why SP must exist before any function
A function cannot run without somewhere to save its return address and locals. No valid SP → the very first function call scribbles into a random box → crash. That is exactly why the hardware loads SP as the first thing at reset, before touching any code.
Compare with the heap in Stack vs Heap memory layout .
A pointer is just a variable whose value is an address — a number that says "the thing is in that box." In C, &x means "the address of x", and *p means "the value in the box that p points at."
Definition Function pointer
A function pointer is a pointer whose target box holds the first instruction of a function . Its C type is written void (*)(void) — read as "address of a function that takes nothing and returns nothing." Putting this address into the PC = calling that function.
Intuition Why the vector table needs this
The vector table (Section 8) is a list of addresses to jump to . An address-of-code is exactly a function pointer, so the whole table is naturally an array of function pointers — except slot 0, which cleverly stores a stack address in the same-shaped slot.
*dst++ = *src++ moves the pointers, not the data."
Why it feels right: you see ++ and think the arrow moves. The fix: it does both, in order — first copy *src into *dst (data), then bump both pointers to the next box. It is the compact heart of the .data copy loop.
Figure s04 lines up the sections and labels each with the action the boot code takes: .text and .rodata (do nothing — already in Flash, read directly), .data in red (COPY Flash→RAM), and .bss (WIPE to 0). The red highlight on .data marks the one section that needs the copy from Figure s02. Reading the figure teaches the mental sorting rule: "what must happen to me at startup?"
Intuition Why several piles and not one
Each pile needs different treatment at startup: .text and .rodata need nothing (already in Flash, read in place), .data needs a copy , .bss needs a wipe . Sorting variables by "what must happen to me at boot" is precisely why these names exist.
Full detail: Linker scripts and memory sections (.text .data .bss) .
Definition Linker and linker symbols
The linker is the tool that runs after the compiler: it decides which address each thing lives at and glues all the compiled pieces into one program image. As it lays out memory, it can also drop named markers called linker symbols — names that stand for addresses (boundaries between regions). The startup code is handed these markers:
_sdata, _edata — s tart / e nd of .data in RAM.
_sbss, _ebss — start / end of .bss in RAM.
_sidata — the Flash address where .data's initial values are stored (the "i nit data " image).
_estack — the top of the stack. The linker script sets it to the first invalid byte just past the end of RAM , rounded to word/stack alignment — i.e. one past the highest usable byte, so the very first push lands on a valid, aligned address inside RAM.
Intuition Why symbols instead of hardcoded numbers
You don't know at coding time how big .data will be — it depends on how many globals you declare. So the startup loop says "copy from _sdata until _edata," letting the linker fill in the real numbers after it lays out memory. The loop bounds are computed, never guessed.
_sdata is a variable holding data."
Why it feels right: it's declared extern uint32_t _sdata;. The fix: we only ever use its address (&_sdata). The symbol's address is the boundary; its "value" is meaningless. That is why startup code writes &_sdata, &_edata, etc.
Details in Linker scripts and memory sections (.text .data .bss) and The C runtime and crt0 .
A vector table is an array of addresses the hardware reads to know where to go. On Cortex-M it sits at a fixed spot (or wherever the VTOR register points). Its first few slots look like this:
Offset
Contents
0x00
initial SP value (top of stack, _estack)
0x04
address of the reset handler (entry point)
0x08
NMI handler address
0x0C
HardFault handler address
0x40+
peripheral interrupt handler addresses
This is the table Figure s01's wall was previewing — each slot is one word, so the offsets march 0x00, 0x04, 0x08, 0x0C, …, exactly the 4-step jumping you saw there.
You now have every piece to read it:
It's an array (Section 1: numbered boxes in a row, 4-byte aligned).
Its entries are addresses of code = function pointers (Section 5).
Except entry 0, which is the stack-top address _estack (Section 4 & 7).
Relocating this table lives in Bootloaders and VTOR relocation . Registers like VTOR are touched via Volatile, memory-mapped registers and hardware init .
Hardware control knobs appear as special boxes in the address wall (memory-mapped registers ). volatile tells the compiler "this box can change on its own — never optimize away reads/writes to it." Startup code that pokes hardware (clocks, VTOR) needs it. See Volatile, memory-mapped registers and hardware init .
How to read this diagram: each box is one foundation from the sections above; an arrow "A → B" means "you need A before B makes sense." Follow any path from the top and you are walking the exact order this page teaches. Every path eventually feeds into the box "Startup code topic" at the bottom — that is the parent note you are preparing for. The two middle merge-points (the reset handler's copy/zero work, and the vector table) are where several foundations come together, so they are the ideas worth over-learning.
Pointers and function pointers
Sections text rodata data bss
Vector table as address array
Reset handler copies and zeros
Test yourself — each should feel obvious before you open the parent note.
An address is the permanent number stamped on one box (byte) in memory.
0x in front of a number meansthe number is written in hexadecimal (base-16).
The underscore inside a number like 0x2000_5000 means nothing about the value — it is just a digit separator for readability, like a comma.
A word on Cortex-M is 4 bytes (32 bits), so words sit at addresses 4 apart.
"Aligned" means the address is a multiple of the value's size; a word must start at a multiple of 4, or the access can fault.
Little-endian means the least-significant byte of a multi-byte value is stored at the lowest address.
Flash keeps its contents when power is off; RAM's contents are undefined at power-up.
The Program Counter (PC) holds the address of the next instruction; jumping = writing a new address into it.
The Stack Pointer (SP/MSP) holds the address of the current top of the stack.
"Full-descending" means SP points at the last item used, and a push subtracts 4 first, then writes — the stack grows downward.
A function pointer is a variable holding the address of a function's first instruction; type void (*)(void).
The reset handler is the first piece of your code that runs after power-on; it copies .data, zeros .bss, then calls main.
The linker is the tool that assigns an address to every function and variable and glues the program together, dropping boundary symbols.
.rodata holdsread-only constants like string literals; they stay in Flash and are read directly, needing no copy or zero.
.data holdsglobals with non-zero initial values, copied from Flash into RAM at startup.
.bss holdszero-initialized globals, wiped to 0 by startup (not stored in Flash).
_estack isthe top of the stack — the first invalid byte just past the end of RAM (rounded to alignment), loaded into MSP at reset.
_sidata isthe Flash address where .data's initial values are stored, to be copied into RAM.
We use &_sdata (its address) not _sdata because linker symbols carry meaning only in their address , marking a region boundary.
*dst++ = *src++ doescopy the value src points at into where dst points, then advance both pointers.