the startup code (crt0 / C runtime startup)
/ see-AR-tee-zero /
Before a stage play begins, someone sets the scene: arranges the props, dims the lights, and puts everyone in position. Only then does the lead actor walk on. On a microcontroller, that scene-setting is the startup code — a small piece of code that runs immediately after reset and prepares the world so that your main() can run with C's normal rules already true. main() is the lead actor; the startup code is the stagehand.
Why is preparation needed at all? Because C programmers take some things for granted that hardware does NOT provide on its own. Two jobs are central. First, the .bss section: in C, global and static variables that you did not initialize (or set to zero) are guaranteed to start as zero. But RAM holds random garbage at power-on. So the startup code must walk over the .bss region in RAM and write zeros into all of it. Second, the .data section: global and static variables WITH a nonzero initial value (like int speed = 5;). The initial values must persist across power-off, so they are stored in flash, but the variable lives in RAM (it is writable). So the startup code copies the initial values from flash into their RAM locations. The linker script provides the start and end addresses for both regions. After zeroing .bss and copying .data — and sometimes setting up the stack, clocks, or the C library — the startup code finally calls main(). The classic name for this on hosted systems is crt0 ('C runtime, object 0').
It matters because if this preparation is skipped or wrong, your program looks correct but misbehaves in baffling ways: a global you 'know' is zero contains garbage, or an initialized constant has the wrong value. The honest subtlety: ZERO-initialized globals and EXPLICITLY-zero globals both go in .bss (so they cost no flash space, only the zeroing pass), while nonzero initializers cost flash for the stored copy. And if a global needs a value before main runs, only the startup code's copy step makes that true — there is no OS loader doing it for you on bare metal.
// What Reset_Handler does, in plain steps: void Reset_Handler(void) { // 1. copy .data initial values from flash to RAM uint32_t *src = &_sidata, *dst = &_sdata; while (dst < &_edata) *dst++ = *src++; // 2. zero the .bss region in RAM for (uint32_t *p = &_sbss; p < &_ebss; p++) *p = 0; // 3. (optional) init clocks, call C library init // 4. hand control to your program main(); for (;;) { } // main should never return }
Zero .bss, copy .data from flash, then call main. The _sdata/_sbss symbols are addresses the linker script defines.
If you ever see an uninitialized global that 'should be zero' holding garbage, suspect the startup code: on bare metal nothing zeroes .bss or copies .data unless your crt0 explicitly does it — there is no OS loader to lean on.