atexit()

I've noticed recently in code I'm reading how little use is made of the atexit() function in C code, it's quite a handy little trick and worth a thought the next time you need to write back-out code to cope with edge cases.

The old ...

Traditionally one might write some code that goes something like this;

main() {
  if(!init_1()) exit(1);
  if(!init_2()) {
    cleanup_1();
    exit(1);
  }
  if(!init_3()) {
    cleanup_2();
    cleanup_1();
    exit(1);
  }
  do_some_work();
  cleanup_3();
  cleanup_2();
  cleanup_1();
  exit(0);
}

Seems quite reasonable, but if you grow the code to say 20 initialisation sections, then each successive failure test is going to need longer and longer lists of cleanup code - you end up with a bit of a beast that doesn't actually do that much.

The new ...

So what if we could write the code like this;

main() {
  if(!init_1()) exit(1);
  if(!init_2()) exit(1);
  if(!init_3()) exit(1);
  do_some_work();
  exit(0);
}

Both more concise, and nicer to read, but at first glance it doesn't do any of our cleanup work. Enter atexit.

All we need to do is to tweak the initialisation code, let's say for example we're opening a connection to a MySQL database, our original code would probably look like this;

MYSQL* conn;
void cleanup_1() {  mysql_close(conn); }

int init_1() {
  conn = mysql_init(NULL);
  return mysql_real_connect(conn,server,user,pass,db,0,NULL,0));
}

All we need to do to use the new mechanism is this;

int init_1() {
  conn = mysql_init(NULL);
  if(mysql_real_connect(conn,server,user,pass,db,0,NULL,0))) {
    atexit(cleanup_1);
    return 1;
  }
  return 0;
}

At atexit queues up functions to be called when the program exits, and it does so in LIFO order. (Last In First Out) So if the code fails half way through the initialisation section, the cleanup routines will be called in the correct order for routines that have been initialised successfully. When the program is complete, all we need do is call exit(), and the atexit stack will unravel calling all the cleanup routines as it goes.

It also means you can exit the code at any point without specific checks to execute cleanup routines, cache flushes, error logging etc, so there's far less chance of a blind exit with data being left in an indeterminate state.

Also worth considering that this mechanism is available in other languages too, next time you try some python code, take a look at import atexit and atexit.register() .. food for thought if you're not doing everything with class destructors .. :-)