Handling Allocation Errors

资源管理与错误处理

Mark Grosberg http://www.conman.org/projects/essays/allocerr.html

Unfortunately, most programmers don't think much about errorhandling. In many situations, error handling code is not eventested. The funny thing is that most of the time, with a littleforethought, error handling can be made generic just like any otherprogramming construct.

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

  • Atomicity
  • Consistancy
  • Isolation
  • Durability

Most commercial database engines guarantee these properties byusing something called a journal. Conceptually, a journal recordswhat the database engine is going to do so that it can be undone (orredone in the event of a crash) if necessary. While the actualmechanics of implementing a journal are not to be discussed here,the idea behind journaling provides a unique solution to errorhandling. In particular, the “undoing” effect of ajournal 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 theerror handling code (after all, how could a malloc()for 81 bytes fail?). This is simply unacceptable in saftey-criticalsoftware (not that it is any more acceptable in non-criticalsoftware).

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

Some languages have garbage collection, but this only solves partof the problem. Leaking resources may be the most obvious case whereerror handling can fail. A more sinister problem is actions thathave been partially completed. In general, an action some either runto completion or it should have no side effects (thats the Ain ACID).

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

One simple solution to fix the problematicOpenFile() function is to put all of the error cleanupas a block and use goto to jump into the propercleanup location. While this solution has its merits (simplicityfor one) it does not work in more complex situations.

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

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


There are many different ways to implement this scheme. A simpleapproach using a union can be used for an efficientimplementation:

/* 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 isno error handling. Do not track more resources than you reservespace 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 removeerror handling code. In a real application, the tracking should bedone by the allocation routines directly. This eliminates all of theconditionals that are repeated on every allocation.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值