What are the basic concepts of what happens before main() is called in C?
On a bare-metal/embedded target, between reset and the first instruction of main() the startup code (often crt0 / a Reset_Handler plus the C runtime) must put the machine into the state the C language assumes. The sequence is roughly:
-
Reset vector. On power-up/reset the CPU fetches a fixed entry from the vector table (or reset vector) and jumps to the reset/startup routine. On Cortex-M, the hardware also loads the initial stack pointer from the first vector-table entry automatically.
-
Set up the stack pointer. Establish SP to a valid RAM region so that function calls and locals work. (On Cortex-M this is the loaded MSP; on other architectures startup code writes SP explicitly.)
-
Initialize the
.datasection. Variables with non-zero initializers live as initial values in flash (the "load address"). Startup copies that block from flash into its RAM location (the "virtual/run address") so globals have their starting values. -
Zero the
.bsssection. Variables with static storage duration and no explicit initializer must read as 0 per the C standard. Startup memsets the.bssregion in RAM to zero. -
Basic hardware / runtime bring-up. Often: configure clocks/PLL, enable the FPU if present, set up the memory controller, and initialize the heap (the allocator's brk/arena) and possibly stack-overflow guards. The exact amount done here varies by platform.
-
Run C++ constructors /
__attribute__((constructor))/.init_array. Walk the.init_array(and legacy.init) tables to run global/static C++ constructors and any C constructor functions before user code. -
Call
main(). Finally the runtime callsmain(argc, argv)(argv typically empty on embedded). Ifmainever returns, the startup code usually runs destructors/atexithandlers and then loops forever or callsexit/_exit.
Key idea: the linker script defines the section addresses (.data load vs. run address, .bss bounds, stack/heap regions), and the startup code uses those symbols to realize the C abstract machine — initialized globals, zeroed globals, a working stack, and constructed statics — before any of your code runs.
