Exercises — Startup code — vector table, reset handler, stack initialization
Before we begin, here is the memory picture every problem leans on. Look at it once, carefully.

Prerequisite links used below: Linker scripts and memory sections (.text .data .bss), ARM Cortex-M exception and interrupt model, Stack vs Heap memory layout, The C runtime and crt0, Bootloaders and VTOR relocation, Volatile, memory-mapped registers and hardware init.
L1 · Recognition
Exercise 1 (L1)
At reset a Cortex-M CPU reads two words from the vector table. The table starts at 0x0000_0000. Word at 0x0000_0000 = 0x2000_8000. Word at 0x0000_0004 = 0x0000_0101. After these two reads, what is in MSP and what is in PC?
Recall Solution
The hardware rule is fixed: word 0 → MSP, word 1 → PC.
- MSP (the stack-top address).
- PC (address of the Reset Handler).
Why word 0 is the stack, not code: the CPU needs a place to push registers before any instruction runs (interrupts can stack registers from cycle zero). So the designers made the very first word the stack pointer. See ARM Cortex-M exception and interrupt model.
Exercise 2 (L1)
The vector table below is written in C. Which entry is not actually a function, and why does the code still compile?
void (* const vector_table[])(void) = {
(void (*)(void)) &_estack, // A
Reset_Handler, // B
NMI_Handler, // C
};Recall Solution
Entry A is not a function — it is the stack-top address _estack. It compiles because the array's element type is "pointer to function taking void returning void", and we cast the address &_estack to that type just to satisfy C's type checker. The hardware never calls word 0; it copies the 32-bit number into MSP. Casting a number to a function-pointer type does not make it code.
L2 · Application
Exercise 3 (L2)
RAM runs from 0x2000_0000 to 0x2000_4FFF (20 KB). The stack is full-descending. What value does the linker assign to _estack, and where does the first pushed word land?
Recall Solution
_estack = highest RAM address + 1 .
Full-descending means a push first decrements SP by 4, then writes. Starting from 0x2000_5000:
So the first pushed word occupies bytes 0x2000_4FFC..0x2000_4FFF — the very top usable slot. Look at the orange arrow in the figure: the stack grows downward into RAM. See Stack vs Heap memory layout.
Exercise 4 (L2)
.data occupies RAM _sdata = 0x2000_0000 to _edata = 0x2000_0010 (exclusive of the end). Each word is 4 bytes. How many words does the copy loop run, and (using the parent's index formula) which Flash address feeds RAM word at 0x2000_000C if _sidata = 0x0800_1000?
Recall Solution
Word count: span in bytes bytes words.
Flash source for RAM 0x2000_000C: the parent's rule is
Here , so the offset bytes.
Why: the n-th word of RAM .data is copied from the n-th word of the Flash image; the offset into RAM equals the offset into Flash. See Linker scripts and memory sections (.text .data .bss).
L3 · Analysis
Exercise 5 (L3)
A student's reset handler copies .data correctly but the .bss-zeroing loop is deleted. The program has int counter; ... counter++; used as a loop bound. It works on their board but hangs on a factory-fresh board. Explain precisely why, in terms of what .bss guarantees.
Recall Solution
.bss holds zero-initialized globals. C guarantees int counter; starts at 0. The reset handler manufactures that guarantee by clearing the .bss RAM region.
With the loop deleted, counter holds whatever was in RAM at power-up — which is undefined. On the student's board that RAM happened to already read 0 (common after a warm reset), so counter began at 0 by luck. On a fresh board, RAM powered up with garbage (say 0xDEAD_BEEF), so counter++ starts from a huge number and the loop bound is wrong → hang.
The fix: restore the zeroing loop for (dst=&_sbss; dst<&_ebss;) *dst++ = 0;. It converts "undefined" into the promised 0. See The C runtime and crt0.
Exercise 6 (L3)
.bss runs from _sbss = 0x2000_0100 to _ebss = 0x2000_0300. The zeroing loop writes one 32-bit word per iteration. How many iterations run, and how many bytes are zeroed?
Recall Solution
Span bytes.
- Bytes zeroed .
- Iterations words.
Why divide by 4: the loop increments a uint32_t* pointer, and dst++ on a uint32_t* advances by sizeof(uint32_t) = 4 bytes. Each iteration writes one word.
Exercise 7 (L3)
The vector table places IRQ handlers starting at offset 0x40. Each entry is one word (4 bytes). At what byte offset does the handler for IRQ5 live, and what memory address is that if VTOR = 0x0800_0000?
Recall Solution
IRQ0 is at offset 0x40. Entry IRQn sits at bytes.
With the table relocated so VTOR = 0x0800_0000:
Why VTOR matters: a bootloader can move the table off 0x0000_0000 and point VTOR at the application's table, so the CPU indexes from the new base. See Bootloaders and VTOR relocation.
L4 · Synthesis
Exercise 8 (L4)
Design the ordering. You must write a reset handler that (a) copies .data, (b) zeros .bss, (c) initializes a memory-mapped clock register that RAM-based code needs, (d) runs C++ constructors, (e) calls main. Two of these have a strict ordering constraint with each other beyond the obvious. Give a correct full order and justify the one non-obvious constraint.
Recall Solution
A correct order:
- (c) clock/hardware init — often first, so the rest runs at full speed and any wait-states are configured. (Uses Volatile, memory-mapped registers and hardware init.)
- (a) copy
.data - (b) zero
.bss - (d) C++ constructors (
__libc_init_array) - (e) call
main
The non-obvious constraint: (d) constructors must come after both (a) and (b). A C++ constructor may read or write a global object — an initialized global lives in .data, a default one in .bss. If constructors ran before the copy/zero, they would touch garbage RAM, and then step (a)/(b) would overwrite whatever the constructor wrote. So .data/.bss setup must finish before any user code (including constructors) executes.
Why (c) can go first: the clock register is a hardware register, not a C global — it does not depend on .data/.bss, so it is safe (and often required) early.
Exercise 9 (L4)
Write the copy-.data loop as a formula sequence for a .data of 3 words with _sdata = 0x2000_0000, _sidata = 0x0800_2000. List every (destination, source) pair the loop produces.
Recall Solution
Using with source , stepping 4 bytes per word:
| word | RAM dest () | offset | Flash source |
|---|---|---|---|
| 0 | 0x2000_0000 |
0x0 |
0x0800_2000 |
| 1 | 0x2000_0004 |
0x4 |
0x0800_2004 |
| 2 | 0x2000_0008 |
0x8 |
0x0800_2008 |
Why the parallel step: both pointers advance by the same 4 bytes each iteration (*dst++ = *src++), so RAM offset and Flash offset stay locked together — that is exactly what the formula expresses.
L5 · Mastery
Exercise 10 (L5)
A device has RAM 0x2000_0000–0x2000_1FFF (8 KB). Layout from the low end up: .data = 256 bytes, .bss = 512 bytes, then the heap, then a gap, then the stack growing down from _estack. The designer wants a guaranteed _Min_Stack_Size = 1 KB and _Min_Heap_Size = 1 KB. Compute _estack, the byte offset where .bss ends, and how many free bytes remain between the top of the reserved heap and the bottom of the reserved stack. State the failure mode if that free region is exhausted.
Recall Solution
Constants: 8 KB bytes; 1 KB bytes.
.dataoccupies0x2000_0000 .. 0x2000_00FF(256 bytes). Ends at offset0x100..bssoccupies the next 512 bytes:0x2000_0100 .. 0x2000_02FF..bssends at offset , i.e. address0x2000_0300.- Reserved heap = 1024 bytes above
.bss:0x2000_0300 .. 0x2000_06FF, ending at offset . _estack= top of RAM + 1 .- Reserved stack = 1024 bytes below
_estack:0x2000_1C00 .. 0x2000_1FFF, its bottom at offset . - Free gap between reserved-heap end and reserved-stack bottom:
Failure mode if the gap is exhausted: with no MMU guard page, a growing stack silently descends past _estack - 1024 into the heap/.bss and corrupts memory — symptoms appear in distant variables, not at the overflow site. Mitigations: linker _Min_Stack_Size check (which is what reserves the 1 KB above), the MSPLIM register on Cortex-M33, or a fill-pattern watermark to detect how far the stack ever reached. See Stack vs Heap memory layout.
Exercise 11 (L5)
Prove numerically that placing main directly at vector-table offset 0x04 (skipping the reset handler) leaves an initialized global wrong. Take int g = 42; at RAM 0x2000_0000, with 42 stored in Flash at _sidata, and assume power-up RAM at 0x2000_0000 reads 0x0000_0000. What value does g have in main in each case (with vs without the copy loop)?
Recall Solution
- With reset handler (copy loop runs): the loop writes Flash's
42into0x2000_0000. Sog == 42. Correct. - Without it (
mainat0x04): the copy never happens;gholds whatever power-up RAM contained =0x0000_0000= 0. Sog == 0, not42. Wrong.
If power-up RAM had held garbage instead of 0, g would be that garbage — even worse. This is exactly why the reset handler is the minimum required glue: it is the only thing that copies .data and zeros .bss. See The C runtime and crt0.
Recall
Word 0 of the vector table goes into which register? ::: MSP (the Main Stack Pointer).
For full-descending stack with _estack = 0x2000_5000, first push lands where? ::: 0x2000_4FFC.
Flash source for RAM .data word at offset d from _sdata? ::: _sidata + d.
Why must C++ constructors run after .data/.bss setup? ::: They are user code that may touch globals; running earlier hits undefined RAM and gets clobbered.