To ensure that static objects are destroyed in the correct order RVCT keeps a record of the correct destruction order. Static objects are global and function static objects.
If a class has a destructor then at the time of construction of each static instance RVCT makes a call to __cxa_atexit() (RVCT2.0) or __aeabi_atexit() (RVCT2.1, 2.2, 3.x and other ABI compliant compilers) which adds a node to a linked list; each node consists of a pointer to the object to be destroyed, the destructor to be called, the dynamic shared object handle and the next node in the list. The 4 words (16 bytes) of memory for each node are obtained by calling malloc().
At shutdown this list is processed by __cxa_finalize(), which is in turn called from __rt_lib_shutdown().
How to avoid this
Here are three suggested ways to avoid the call to malloc (and free);
1. Retarget the appropriate __..._atexit() function and return without doing anything. This means that the destructors for global and function static objects will not be called when main()exits, but this is acceptable in many systems. You should return a non-zero value to indicate the function succeeded.
2. Remove destructors from all classes that are instantiated as global or function static objects and place the destructor code in a separate member function which is called directly by the program at shutdown. If these classes are also used as locals then two versions of the class could be created e.g.
#include <iostream> /* For std::cout */
/* StaticPoint has destruction code in destroy() */
class StaticPoint {
public:
StaticPoint(int a, int b) {
x=a;
y=b;
}
void destroy(void) {
std::cout << "Point " << x << ", " << y << " destroyed";
x = y = 0;
}
void move(int a,int b) {
x+=a;
y+=b;
}
private:
int x, y;
};
/* Point inherits everything from StaticPoint
but has a destructor which calls destroy() */
class Point : public StaticPoint {
public:
Point(int a, int b) : StaticPoint(a, b) {}
~Point() {
StaticPoint::destroy();
}
private:
void destroy(void); /* Stop anyone calling destroy() directly */
};
/* Test Program */
StaticPoint p1(1,2);
StaticPoint p2(4,5);
int main(void) {
Point p3(8,9);
p3.move(1,2);
//p3.destroy(); /* Not possible because destroy() is private in Point */
p1.destroy();
p2.destroy();
return 0; /* p3 destroyed automatically */
}
3. Retarget the appropriate version of the __..._atexit() function and __cxa_finalize() to use statically allocated memory to store the linked list.
RVCT 2.0 : int __cxa_atexit(&destructor, &object, &__dso_handle);
RVCT 2.1, 2.2, 3.x and other ABI compliant compilers : int __aeabi_atexit(&object, &destructor, &__dso_handle);
The return value should be non-zero to indicate success (although this is not currently checked).
__dso_handle is only useful if modules of code are being dynamically loaded and unloaded. It should be a unique word within each dynamic shared object and is used to call the destructors of all objects created by a module before unloading it (see below). This argument can be set to NULL if dynamic loading and unloading are not being used. When the program terminates __cxa_finalize() is called by the library and is responsible for calling destructors in the correct order.
void __cxa_finalize(&dso_handle);
In the normal situation &dso_handle will be NULL and all destructors will be called. If called with a non NULL argument only the destructors of the matching shared object should be called. Special consideration may be needed in the retargeted __cxa_finalize() if the destruction of one object can cause the list of destructors to change (i.e. if a destructor created a static object).
The standard C function atexit() also works by adding nodes to the destructor list, and would need retargeting if it is used.