Static Objects
Let's analyse the code that initialises static objects. test_cpp_statics is a simple application that has two static objects, one is in the global scope, the other is in the function scope.
Note, that compiler will try to inline the code above if implemented in the same file. To properly analyse the code that initialises global variables, you should put implementation of constructor and instanceGlobal()
/instanceLocal()
functions into separate files. If -nostdlib
option is passed to the compiler to exclude linking with standard library, the compilation of the code above will fail with following error:
It means that compiler attempts to make static variables initialisation thread-safe. The get it compiled you have to either implement the locking functionality yourself or allow compiler to do it in an unsafe way by adding -fno-threadsafe-statics
compilation option. I think it is quite safe to use this option in the bare-metal development if you make sure the statics are not accessed in the interrupt context or have been initialised at the beginning of main()
function before any interrupts are enabled. To grab a reference to such object without any use is enough:
Now, let's analyse the initialisation of globalObj
. The .init.array
section contains pointer to initialisation function _GLOBAL__sub_I__ZN7SomeObj9globalObjE
.
Disassembly of section .init.array:
The initialisation function loads the address of the object and passes it to the constructor of SomeObj
together with the initialisation parameters (“1” and “2” integer values).
The code above loads the address of the global object (0x00008168
) into r0, and initialisation parameters into r1 and r2, then invokes the constructor of SomeObj
.
Please remember to call all the initialisation functions from .init.array
section in your startup code before calling the main()
function.
In the linker file:
In the startup code:
However, if standard library is NOT excluded explicitly from the compilation, the __libc_init_array
provided by the standard library may be used:
Let's also perform analysis of initialisation of localObj
in SomeObj::instanceLocal()
.
The code above loads the address of the flag that indicates that the object was already initialised into r4, then loads the value into r3 and checks it using tst
instruction. If the flag indicates that the object wasn't initialised, the constructor of the object is called and the flag value is updated prior to returning address of the object. Note that tst r3, #1
instruction performs binary AND between value r3 and integer value #1, then next bne
instruction performs branch if result is not 0, i.e. the object was already initialised.
CONCLUSION: Access to global objects are a bit cheaper than access to local static ones, because access to the latter involves a check whether the object was already initialised.
Custom Destructors
And what about destruction of static objects with non-trivial destructors? Let's add a destructor to the above class and try to compile:
Somewhere in *.cpp file:
This time the compilation will fail with following errors:
According to this document, the __aeabi_atexit
function is used to register pointer to the destructor function together with pointer to the relevant static object to be destructed after main
function returns. The reason for this behaviour is that these objects must be destructed in the opposite order to which they were constructed. The compiler cannot know the exact construction order for local static objects. There may even be some static objects are not constructed at all. The __dso_handle
is a global pointer to the current address where the next {destructor_ptr, object_ptr} pair will be stored. The main
function of most bare metal applications is not supposed to return and global/static objects will not be destructed. In this case it will be enough to implement the required function the following way:
However, if your main
function returns and then the code jumps back to the initialisation/reset routine, there is a need to properly perform destruction of global/static objects. You'll have to allocate enough space to store all the necessary {destructor_ptr, object_ptr} pairs, then in __aeabi_atexit
function store the pair in the area pointed by __dso_handle
, while incrementing value of later. Note, that dso_handle
parameter to the __aeabi_atexit
function is actually a pointer to the global __dso_handle
value. Then, when the main
function returns, invoke the stored destructors in the opposite order while passing addresses of the relevant objects as their first arguments.
To verify all the stated above let's take a look again at the generated code of initialisation function (after the destructor was added):
Indeed, the call to the constructor immediately followed by the call to __aeabi_atexit
with address of the object in r0 (first parameter), address of the destructor in r1 (second parameter) and address of __dso_handle
in r2 (third parameter).
CONCLUSION: It is better to design the “main” function to contain infinite loop and never return to save the implementation of destructing global/static objects functionality.
Last updated