ARM Technical Support Knowledge Articles

Why does malloc() get called when global C++ objects are initialised at startup?

Applies to: DS-5, RealView Developer Kit (RVDK) for OKI, RealView Developer Kit (RVDK) for ST, RealView Development Suite (RVDS)

Answer

To ensure that static objects are destroyed in the correct order, RVCT/ARM Compiler keeps a record of the correct destruction order. Static objects are global.

If a class has a destructor then at the time of construction of each static instance RVCT makes a call to __cxa_atexit() (RVCT 2.0) or __aeabi_atexit() (RVCT 2.1 and later compilers, 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, for example:

    #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 and later compilers, 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.

Article last edited on: 2012-03-20 16:34:53

Rate this article

[Bad]
|
|
[Good]
Disagree? Move your mouse over the bar and click

Did you find this article helpful? Yes No

How can we improve this article?

Link to this article
Copyright © 2011 ARM Limited. All rights reserved. External (Open), Non-Confidential