IBM Z and LinuxONE - Languages - Group home

Be careful when using the atexit() routine with "static" objects

By FANG LU posted Tue March 24, 2020 07:39 PM


 Recently, while helping a client debug their application, we ran into a scenario where atexit() was being used with "static" objects. Interestingly enough, the position of the atexit() routine was affecting the order of the destructor calls for their static objects. Some applications may depend on a specific order of destructor calls. So if atexit() is not used properly, it can cause unexpected runtime behavior. 


Let's consider the following example:

$cat test.cpp

1  #include <iostream>


3  using namespace std;


5  class Test {


7  public:


9  ~Test() {


11  cout << "In Test destructor" << endl;

12  }


14  static Test& Instance() {


16  static Test theInstance;

17  return theInstance;

18  }

19  };


21  void cleanup() {


23  cout << "In cleanup function" << endl;

24  }


26  int main(int argc, char *argv[]) {


28  #ifdef CASE1

29   atexit(cleanup);

30   Test::Instance();

31  #endif

32  #ifdef CASE2

33   Test::Instance();

34   atexit(cleanup);

35  #endif


37  return 0;

38  }



For CASE1, you will see the following output at runtime:


In Test destructor

In cleanup function



and for CASE2, you will see the following output at runtime:


In cleanup function

In Test destructor



As a user, one might always expect the cleanup function call to occur first ie, where the function registered with atexit() is processed before the static object is destroyed. However, this is not the case.


According to the C++ Standard, for an object with static storage duration constructed after a function is registered with atexit(), then following the call to exit, the registered function is not called until the execution of the object’s destructor has completed (ie CASE1).


If a function is registered with atexit() then following the call to exit, any objects with static storage duration initialized prior to the registration of that function shall not be destroyed until the registered function is called from the termination process and has completed (ie CASE2).


So, next time when using the atexit() function, be sure to keep this in mind to avoid potential runtime problems!