Consider the following code:
#include <iostream>
#include <stdio.h>
void preinit_void();
void init_void();
__attribute__((section(".init_array")))
void (*p_init)(void) = &init_void;
void constructor_void() __attribute__((constructor));
void constructor_void() {
printf(__FUNCTION__);
}
__attribute__((section(".preinit_array")))
void (*p_preinit)(void) = &preinit_void;
void preinit_void() {
printf(__FUNCTION__);
}
void init_void() {
printf(__FUNCTION__);
}
int main() {
std::cout << __FUNCTION__ << '\n';
}
On running the code the output is
preinit_voidinit_voidconstructor_voidmain
If I were to change the code to:
#include <iostream>
#include <stdio.h>
void preinit_void();
void init_void();
void constructor_void() __attribute__((constructor));
void constructor_void() {
printf(__FUNCTION__);
}
__attribute__((section(".init_array")))
void (*p_init)(void) = &init_void;
__attribute__((section(".preinit_array")))
void (*p_preinit)(void) = &preinit_void;
void preinit_void() {
printf(__FUNCTION__);
}
void init_void() {
printf(__FUNCTION__);
}
int main() {
std::cout << __FUNCTION__ << '\n';
}
The output changes to:
preinit_voidconstructor_voidinit_voidmain
I am confused as to which section is initialized first. Does the output change merely due to parsing by compiler (that it found .init_array
section first instead of the constructor
) or is there a proper initialization sequence for it?
On current (GNU) systems, references to ELF constructor functions are placed into the .init_array
section, just like the entries you add manually. This why the executing order changed when you changed the source code order. GCC and binutils have a language extension for using separate sections based on priority, and the link editor will serialize everything into a final .init_array
section for each application/shared object (based on source code and link order, after sorting by priority).
The constructor arrays for different shared objects are executed after performing a topological sort based on the dependencies between objects (as expressed through the DT_NEEDED
tag), so that objects are initialized after their dependencies.
This goes way beyond what is required by C++ for initialization, but it is more or less specified in various (GNU) ABI-related documents, and it is not going to change. Some aspects might change due to the introduction of new features (e.g., multiple DF_1_INITFIRST
objects, although this one is a bit baffling), but they will hopefully only affect processes which actually use those features.