Course contentsShow
← Embedded Systems & Computer Architecture
Lesson 457 of 9366. Embedded Software Construction and ToolchainsFree lesson

Managing Static Constructors in C++ Environments

Traverse the linked list of static constructor pointers to ensure that global objects are properly instantiated before the main function runs.

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.