In embedded development, many engineers often wonder: after powering on the program, how are global and static variables initialized? Is the underlying memory operation logic the same from Keil MDK-ARM to the GCC cross-toolchain? Today, we will break down this core issue and understand the complete process from chip power-up to the execution of the main() function.
1. Understanding the Basics: What Does Memory Initialization Do?
Before discussing specific toolchains, we must clarify a key point: the memory initialization of embedded programs essentially accomplishes two tasks:
-
Copying the
.datasection: This involves copying the “initialized global/static variables” stored in Flash (for example,int a = 10;) to a specified location in RAM — because RAM loses data when powered off, it needs to “restore” initial values from Flash after power-up. -
Zeroing the
.bsssection: This means setting all “uninitialized or initialized to 0 global/static variables” in RAM (for example,int b;orint c = 0;) to 0 — this is the default behavior specified by the C language standard.
These two steps are fundamental to the C runtime environment; without them, the values of global variables would be random, and the program would not run correctly. The core difference between different toolchains lies in “who performs these two operations”.
2. Keil MDK-ARM: __main as the Behind-the-Scenes Driver
If you develop ARM chips using Keil, you may have encountered the __main function, but few delve into its role — it is actually the “overall manager” of memory initialization in the Keil environment.
1. The Complete Startup Process of Keil
The entire process from chip power-up to the execution of main() is divided into two major phases, which we clearly illustrate with a flowchart:

2. Key Details Breakdown
-
First Phase: Hardware + Assembly Startup File After the chip powers on, the hardware first does one thing: it reads the initial stack pointer (MSP) from the first address of the “interrupt vector table”, and then reads the reset handler address (usually pointing to
Reset_Handler) from the second address. It then executes the assembly startup file (such asstartup_stm32f103.s), which is provided by the chip manufacturer, and its core function is to initialize the exception vector table, finally jumping to__main— note! Here,__mainis a function from the Keil C library, not themain()you wrote. -
Second Phase: Core Work of
__main__mainis the entry point of the Keil C library, and it automatically completes three key tasks: ① It copies the.datasection from Flash to RAM according to the addresses defined in the “scatter loading file (.sct)”; ② It zeroes out the entire.bsssection in RAM; ③ If it is a C++ project, it calls the constructors of global objects; only then does it call themain()function we wrote.
3. Common Misconceptions Clarified
-
__mainvsmain(): The former is the initialization entry point of the C library and cannot be modified; the latter is your application entry point, called by__main. -
Source of Stack and Heap: The addresses and sizes of the stack/heap are defined in the
.sctfile, and__mainwill directly use these configurations without manual initialization.
3. GCC Cross Toolchain: _start Takes the Baton
Many engineers switching to GCC (such as STM32CubeIDE, VSCode+GCC) mistakenly believe that “GCC requires manual memory initialization” — this is actually a common misunderstanding. Like Keil, GCC also has an automatic initialization mechanism, but the core function has changed to _start.
1. Standard Startup Process of GCC
The startup logic of GCC is similar to that of Keil, primarily relying on the “C runtime startup code (Crt0)”. The process is as follows:

2. Key Details Breakdown
-
Where is
_startHidden?_startis usually located in the precompiled filecrt0.oprovided by the GCC toolchain, and during compilation, the toolchain automatically links it into the program without requiring you to add it manually.
-
Core Differences with Keil The essential logic is consistent (copying
.data, zeroing.bss), but there are two differences: ① The names of the initialization entry functions differ: Keil uses__main, while GCC uses_start; ② The configuration files differ: Keil uses a.sctscatter loading file, while GCC uses a.ldlinker script (the addresses of.data/.bssare defined in the.ld).
3. When is Manual Implementation Needed?
To emphasize: the vast majority of GCC projects do not require manual memory initialization! Only the following special scenarios require it:
-
Extremely Minimal Bare-Metal Programs: For example, programs that do not rely on the standard C library at all (
-nostdlib), in which case you need to write assembly code to complete the.datacopy and.bsszeroing before jumping tomain(). -
Custom Memory Layout: For example, if the
.datasection needs to be copied from QSPI Flash to SDRAM, the standardcrt0.ocannot handle this, requiring modifications to the.ldfile and writing custom initialization code. -
Bootloader First Stage (SPL): For example, U-Boot’s SPL needs to run before initializing RAM and can only use pure assembly + register operations, without relying on standard initialization.
Manual Implementation Example (ARM Assembly)
If manual operations are indeed necessary, the core code logic is as follows (symbols need to be defined in the .ld file):

4. Comparison of Keil and GCC
To provide a clearer comparison, I have organized the key features of the two toolchains:
| Comparison Dimension | Keil MDK-ARM | GCC Cross Toolchain |
|---|---|---|
| Memory Initialization Entry Function | __main (provided by C library) |
_start (usually in crt0.o) |
| Default Need for Manual Initialization | No (fully automatic) | No (fully automatic) |
| Configuration File Type | Scatter loading file (.sct) | Linker script (.ld) |
| Scenarios for Manual Implementation | Using micro libraries, fully bare-metal development | Deep bare-metal, custom memory, Bootloader SPL |
| C++ Global Constructor Calls | __main handles automatically |
_start handles automatically (depends on .init_array section) |
5. Conclusion
-
Whether using Keil or GCC, manual memory initialization is not required by default — the toolchains automatically complete the
.datacopy and.bsszeroing through__main(Keil) or_start(GCC), allowing developers to focus solely on the business logic inmain(). -
Manual initialization is an “advanced option” used only for extremely minimal bare-metal programs, custom memory layouts, or Bootloader development, and is not a routine operation.
-
Understanding the startup process is key to debugging low-level issues: If you encounter abnormal global variable values or program startup crashes, consider checking the initialization logic for
.data/.bss, and verify whether the linker scripts (.sct/.ld) or startup files are configured correctly.
I hope this article helps you clarify the underlying logic of embedded program startup.