Search code examples
cerror-handlingcoding-stylegotocontrol-flow

Flow controlling macros with 'goto'


Yes, two hated constructs combined. Is it as bad as it sounds or can it be seen as a good way to control usage of goto and also provide a reasonable cleanup strategy?

At work we had a discussion about whether or not to allow goto in our coding standard. In general nobody wanted to allow free usage of goto but some were positive about using it for cleanup jumps. As in this code:

void func()
{
   char* p1 = malloc(16);
   if( !p1 )
      goto cleanup;

   char* p2 = malloc(16);
   if( !p2 )
      goto cleanup;

 goto norm_cleanup;

 err_cleanup:

   if( p1 )
      free(p1);

   if( p2 )
      free(p2);

 norm_cleanup:
}

The abovious benefit of such use is that you don't have to end up with this code:

void func()
{
   char* p1 = malloc(16);
   if( !p1 ){
      return;
   }

   char* p2 = malloc(16);
   if( !p2 ){
      free(p1);
      return;
   }

   char* p3 = malloc(16);
   if( !p3 ){
      free(p1);
      free(p2);
      return;
   }
}

Especially in constructor-like functions with many allocations this can sometimes grow very bad, not the least when someone has to insert something in the middle.

So, in order to be able to use goto, but still clearly isolate it from being used freely, a set of flow controlling macros was created for handling the task. Looks something like this (simplified):

#define FAIL_SECTION_BEGIN int exit_code[GUID] = 0;
#define FAIL_SECTION_DO_EXIT_IF( cond, exitcode ) if(cond){exit_code[GUID] = exitcode; goto exit_label[GUID];}
#define FAIL_SECTION_ERROR_EXIT(code) exit_label[GUID]: if(exit_code[GUID]) int code = exit_code[GUID];else goto end_label[GUID]
#define FAIL_SECTION_END end_label[GUID]:

We can use this as follows:

int func()
{
   char* p1 = NULL;
   char* p2 = NULL;
   char* p3 = NULL;

   FAIL_SECTION_BEGIN
   {
      p1 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p1, -1 );

      p2 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p2, -1 );

      p3 = malloc(16);
      FAIL_SECTION_DO_EXIT_IF( !p3, -1 );
   }
   FAIL_SECTION_ERROR_EXIT( code )
   {
      if( p3 ) 
         free(p3);

      if( p2 ) 
         free(p2);

      if( p1 ) 
         free(p1);

      return code;
    }
    FAIL_SECTION_END

  return 0;

It looks nice, and comes with many benefits, BUT, are there any drawbacks we should be thinking about before rolling this out into development? It is after all very flow controlling and goto:ish. Both are discouraged. What are the arguments for discouraging them in this case?

Thanks.


Solution

  • Error handling is one of the rare situations when goto is not so bad.

    But if I had to maintain that code I would be very upset that goto are hidden by macros.

    So in this case goto is OK for me but not macros.