The answer from Robotbugs is interesting, but I found some additional information that might satisfy the curiosity of others.
The System V Application Binary Interface seems to apply to the executables produced by GCC (and I guess some other compilers - clang comes to mind).
The special sections chapter states (only relevant parts, reordered by me):
.preinit_array:
This section holds an array of function pointers that contributes to a single pre-initialization array for the executable or shared object containing the section.
.init_array
This section holds an array of function pointers that contributes to a single initialization array for the executable or shared object containing the section.
.fini_array
This section holds an array of function pointers that contributes to a single termination array for the executable or shared object containing the section.
The file init.c from newlib includes:
/* Iterate over all the init routines. */
void
__libc_init_array (void)
{
size_t count;
size_t i;
count = __preinit_array_end - __preinit_array_start;
for (i = 0; i < count; i++)
__preinit_array_start[i] ();
#ifdef HAVE_INIT_FINI
_init ();
#endif
count = __init_array_end - __init_array_start;
for (i = 0; i < count; i++)
__init_array_start[i] ();
}
This corresponds to the canonical linker script solution for STM32 processors (as an example):
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
That linker script portion is quite clear: it defines the symbols necessary for Newlib to execute the array functions specified by the System V Application Binary Interface for preinit and init. This seems to be the standard solution for static constructors in C++. And fini would corresponds to static destructors.
The most ironic part of this story, of course, is that using static C++ objects without the Construct On First Use Idiom is the best way to get the static initialization order problem! I.e. C++ objects should in practice never be constructed via the preinit/init array above!