Managing Static Constructors in C++ Environments
In this lesson, you will learn how to manually trigger the execution of C++ static constructors before the main() function begins in an embedded system.
In desktop environments, the operating system's loader handles the invocation of global constructors. However, in bare-metal embedded systems, your application is responsible for orchestrating the transition from a cold boot to a fully initialized C++ runtime.
The Linker Script's Role
To manage static objects, the linker script typically collects all constructor function pointers into a dedicated memory section, often named .init_array. Your linker script defines the boundaries of this section using symbols:
.init_array : {
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} > FLASH
By using KEEP, you ensure the linker doesn’t discard these pointers as "unused code." The SORT command ensures the constructors are executed in the correct order (usually defined by the priority of the global objects).
Traversing the Constructor List
Once you have defined __init_array_start and __init_array_end, your startup code (invoked after initializing hardware and copying data sections to RAM) must iterate through these pointers. Each address in this range represents a function pointer to a static constructor.
You can implement the traversal in your startup file like this:
extern "C" void run_static_constructors() {
typedef void (*constructor_func)();
extern constructor_func __init_array_start[];
extern constructor_func __init_array_end[];
size_t count = __init_array_end - __init_array_start;
for (size_t i = 0; i < count; i++) {
__init_array_start[i]();
}
}
By calling this function after your .data section is initialized but before branching to main(), you ensure that all global C++ objects are correctly instantiated and their internal state is ready for use. If you skip this, any global object accessing hardware or memory will likely lead to undefined behavior or a hard fault, as their constructors would never have executed.
Key Takeaway: You must manually iterate through the .init_array section using linker-defined boundary symbols to initialize global C++ objects before the application entry point.