From Keil to GCC: Who Handles Memory Initialization During Embedded Program Startup?

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:

  1. Copying the .data section: 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.

  2. Zeroing the .bss section: This means setting all “uninitialized or initialized to 0 global/static variables” in RAM (for example, int b; or int 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:

From Keil to GCC: Who Handles Memory Initialization During Embedded Program Startup?

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 as startup_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, __main is a function from the Keil C library, not the main() you wrote.

  • Second Phase: Core Work of __main __main is the entry point of the Keil C library, and it automatically completes three key tasks: ① It copies the .data section from Flash to RAM according to the addresses defined in the “scatter loading file (.sct)”; ② It zeroes out the entire .bss section in RAM; ③ If it is a C++ project, it calls the constructors of global objects; only then does it call the main() function we wrote.

3. Common Misconceptions Clarified

  • __main vs main(): 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 .sct file, and __main will 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:

From Keil to GCC: Who Handles Memory Initialization During Embedded Program Startup?

2. Key Details Breakdown

  • Where is _start Hidden? _start is usually located in the precompiled file crt0.o provided 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 .sct scatter loading file, while GCC uses a .ld linker script (the addresses of .data/.bss are 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 .data copy and .bss zeroing before jumping to main().

  • Custom Memory Layout: For example, if the .data section needs to be copied from QSPI Flash to SDRAM, the standard crt0.o cannot handle this, requiring modifications to the .ld file 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):

From Keil to GCC: Who Handles Memory Initialization During Embedded Program Startup?

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

  1. Whether using Keil or GCC, manual memory initialization is not required by default — the toolchains automatically complete the .data copy and .bss zeroing through __main (Keil) or _start (GCC), allowing developers to focus solely on the business logic in main().

  2. 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.

  3. 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.

Leave a Comment