Conman Laboratories

Better living through software …

Handling Allocation Errors

Mark Grosberg

Unfortunately, most programmers don't think much about error handling. In many situations, error handling code is not even tested. The funny thing is that most of the time, with a little forethought, error handling can be made generic just like any other programming construct.

Looking back on history, programmers implementing databases have always had to guarantee what are called the ACID properties:

Most commercial database engines guarantee these properties by using something called a journal. Conceptually, a journal records what the database engine is going to do so that it can be undone (or redone in the event of a crash) if necessary. While the actual mechanics of implementing a journal are not to be discussed here, the idea behind journaling provides a unique solution to error handling. In particular, the “undoing” effect of a journal is exactly what most error handling code in programs (well, those that attempt to recover from errors) does.

Lets look at a typical routine from a ficticious text editor:

typedef struct
{
  FILE     *file;
  char     *name;

  char     *line_buffer;
  char     *body_buffer;
} EDITBUFFER;

BOOL OpenFile(EDITBUFFER *eb, char *fn)
{
  long  file_size;

  /* First, copy the file name */
  eb->name = malloc(strlen(fn) + 1);
  if (eb->name == NULL)
     return FALSE;
  strcpy(eb->name, fn);

  /* Now, open the file. */
  eb->file = fopen(fn, "r+");
  if (eb->file == NULL)
   {
      free(eb->name);

      return FALSE;
   }

  /* Create the memory buffers. */
  eb->line_buffer = malloc(81); 
  if (eb->line_buffer == NULL)
   {
      free(eb->name);
      fclose(eb->file);

      return FALSE;
   }
  
  /* Since this could fail, check the return code. */
  file_size = GetFileSize(eb->file);
  if (file_size == NULL)
   {
      free(eb->name);   
      fclose(eb->file);
      free(eb->line_buffer);

      return FALSE;
   }
  eb->body_buffer = malloc(file_size);
  if (eb->body_buffer == NULL)
   { 
      free(eb->name);    /* Notice this code is duplicated. */
      fclose(eb->file); 

      return FALSE;
   }

  return TRUE;
}

The majority of that function is actually error handling code. Some programmers get rather tedious of this and simply omit the error handling code (after all, how could a malloc() for 81 bytes fail?). This is simply unacceptable in saftey-critical software (not that it is any more acceptable in non-critical software).

Even those programmers that don't mind writing code like the above can make mistakes and potentially cause a failure at worst (or leak resources at best). When programming, you should always say to yourself: “This is a computer, it is here to do the dirty work for me, not the other way around.” It may sound silly, but many programmers just don't think that way.

Some languages have garbage collection, but this only solves part of the problem. Leaking resources may be the most obvious case where error handling can fail. A more sinister problem is actions that have been partially completed. In general, an action some either run to completion or it should have no side effects (thats the A in ACID).

For example, if a routine that creates a file fails after creation of the file (but before completing successfully) the file should be automatically deleted. Garbage collection (at least most implementations) can not solve this problem. Journaling does.

One simple solution to fix the problematic OpenFile() function is to put all of the error cleanup as a block and use goto to jump into the proper cleanup location. While this solution has its merits (simplicity for one) it does not work in more complex situations.

What we really want for the computer to track resources as we allocate them. If we detect a failure, the computer should automatically clean up the resources. The problem is how does the computer know if I want a file closed, deleted, or perhaps copied to a backup file. Simple answer: You tell it!

Each resource keeps a function pointer that it associates with the resource. In the event of an error the list is walked and the function associated with each pending resource is called. The function can be set to perform different actions using internal logic. Alternatively, different functions to perform different levels of cleanup for the same type of resource can be written.


There are many different ways to implement this scheme. A simple approach using a union can be used for an efficient implementation:

/* This calculates the number of elements in an array. */
#define NUMELEM(a)    (sizeof(a) / sizeof(a[0]))

typedef void (*CleanupFunction)(void *Resources);
typedef struct
{
  void            *Resource;
  CleanupFunction  Cleaner;
} Allocation;
typedef struct
{
  size_t      Size, Count;
} ResourceListHead;
typedef union
{
  ResourceListHead  head;   /* This must come first so we can initialize it */
  Allocation        alloc;
} ResourceList;

void TrackResource(ResourceList *list, void *Resource, CleanupFunction Cleaner)
{
   size_t  next;

   if (list[0].head.Count != list[0].head.Size)
    {
      next = (list[0].head.Count)++;
   
      list[next].alloc.Resource = Resource;
      list[next].alloc.Cleaner  = Cleaner;
    }
}

void DestroyResources(ResourceList *list)
{
   size_t  count;
   
   count = list[0].head.Count;
   for(list++; count; count--)
    {
      /* Call the cleanup function. */
      (list->alloc.Cleaner)(list->alloc.Resource);

      list++;
    }
}

This is the most minimal resource tracking you can get. There is no error handling. Do not track more resources than you reserve space for. As an example of how to use this (specific) implementation, I present the problematic OpenFile() function using these resource lists:

/* These would (normally) be in a runtime library somewhere */
void CleanMalloc(void *buf)
{
  free(buf);
}

void CloseFile(void *buf)
{
  fclose((FILE *)buf);
}

BOOL OpenFile(EDITBUFFER *eb, char *fn)
{
  /* 
      * We always need one more structure than we plan to track for
      * the header. Also, we must initialize the first element so that the 
      * list is considered empty. 
      */
  ResourceList   allocs[6] = {  NUMELEM(allocs) - 1, 0 }; 
  long           file_size;

  /* First, copy the file name */
  eb->name = malloc(strlen(fn) + 1);
  if (eb->name == NULL)
     goto error;
  TrackResource(allocs, eb->name, CleanMalloc);
  strcpy(eb->name, fn);

  /* Now, open the file. */
  eb->file = fopen(fn, "r+");
  if (eb->file == NULL)
     goto error;
  TrackResource(allocs, eb->file, CloseFile);
 
  /* Create the memory buffers. */
  eb->line_buffer = malloc(81); 
  if (eb->line_buffer == NULL)
     goto error;
  TrackResource(allocs, eb->line_buffer, CleanMalloc);  

  /* Since this could fail, check the return code. */
  file_size = GetFileSize(eb->file);
  if (file_size == NULL)
     goto error;

  eb->body_buffer = malloc(file_size);
  if (eb->body_buffer == NULL)
     goto error;

  return TRUE;

error:
  DestroyResources(allocs);
  return FALSE;  
}

That is only a small example of how resource lists can remove error handling code. In a real application, the tracking should be done by the allocation routines directly. This eliminates all of the conditionals that are repeated on every allocation.