Monday, June 18, 2012

Exception handling in C

Adding exception handling in C is a rather controversial topic. Some think that exceptions are pure evil and should never be considered, others say that those wanting to use exceptions should leave the realm of pure C and fly to C++ (or, even, Java). Others suggest that goto is a perfectly fine way to deal with exceptions.

I think that there are quite some occasions when the try/catch mechanism offered by C++ (and other languages) can make code clearer.
Let me go back to what I believe is the the #1 job of programmers:

To write code as simple and as clean as possible so that the code itself conveys its purpose.

We all know how much this is easier said than done. I believe that anything that could help in expressing oneself clearer is beneficial.

The first scenario that comes to mind is the need of acquire a set of resources (e.g. memory, files, connections,...). If a resource is not available, the ones previously acquired should be released. This is usually solved using nested if or using a goto to a specific cleanup section of the code. Personally I don't like neither one or the other and I believe that using try/catch would make the intent clearer.

If C had them, that is!

Actually, it turns out it's not so difficult to put together few macros to do it. C-libutl has them, they are very easy to use and only require a C99 compiler. Here is an example:

   #define EXCEPTION_NOMEMORY    1
   #define EXCEPTION_NORESOURCE  2
   #define EXCEPTION_NOFILE      3
   #define EXCEPTION_FATALERROR  5

   int main(int argc, char *argv[])
   {
     ...
     try {
        printf("Do something\n");
        f = fopen("myfile","r");
        if (f == NULL) throw(EXCEPTION_NOFILE);
        printf("Continue doing\n");
      } 
      catch (EXCEPTION_NOMEMORY) {
         printf("Not enough memory\n");
      }
      catch (EXCEPTION_NOFILE) {
         printf("Unable to find file\n");
      }
      ...
   }

I'm sure you guessed that if "myfile" doesn't exist this fragment of code will print:

       Do Something
       Unable to find file

So far it doesn't seem too useful: it's just a sort of goto in disguise! Or, even better, this could be handled with a simple if.
Actually, having try/catch blocks become more useful when you consider that you can throw an exception from a function and having it handled in the calling function:

      void do_it(void)
      {
         ...
         m = malloc(128);
         if ( m == NULL) throw(EXCEPTION_NOMEMORY);
         ...
      }

      int main(int argc, char *argv[])
      {
        ...
        try {
          printf("Do something\n");
          do_it();
          printf("Completed\n");
        } 
        catch (EXCEPTION_NOMEMORY) {
           printf("Not enough memory\n");
        }
        catch (EXCEPTION_NOFILE) {
           printf("Unable to find file\n");
        }
        catchall {
           printf("Something unexpected happened\n");
        }
        ...
      }

Now, if whitin the do_it() function the system runs out of memory, the fragment will print:

       Do Something
       Not enough memory

A rather useful mechanism to handle herrors neatly ... or for messing everything up! As a C programmer you're used to sharp tools, I'm sure you'll find a responsible way of using it.

The catchall block at the end will be executed if an exception is thrown but is not managed earlier.

In a catch block, you can retrieve the exception code through the function thrown() (actually this is useful only in a catchall block.

Try/catch blocks can be nested but, usually, this is a rather bad idea. What is useful, instead, is the ability to pass the exception up to have it handled from the calling function like in this example:

      void do_it(void)
      {
        ...
        try {
          ...
          m = malloc(128);
          if ( m == NULL) throw(EXCEPTION_NOMEMORY);
          ...
          f = fopen("myfile","r");
          if (f == NULL) throw(EXCEPTION_NOFILE);
          ...
        } 
        catch (EXCEPTION_NOFILE) {
          f = stdin;
          free(m); // Continue in this function
        }
        catchall {
          rethrow(); // Excpetion will be handled in the calling function
        }
        ...
      }

      int main(int argc, char *argv[])
      {
        ...
        try {
          printf("Do something\n");
          do_it();
          printf("Completed\n");
        } 
        catch (EXCEPTION_NOMEMORY) {
           printf("Not enough memory\n");
        }
        catch (EXCEPTION_NOFILE) {
           printf("Unable to find file\n");
        }
        ...
      }
In the do_it() function, the exception NOFILE is handled directly but the exception NOMEMORY is passed to the upper try/catch block via the rethrow() function.

*  *  *  *

Using exceptios can be a neat way to propagate errors through function calls. The alternative is to define specific error codes to be returned by the functions, or to pass additional parameters to functions for error handling (or use global variables!). All these solutions could lead to messy code, where the error handling logic obscures the actual program logic. In such cases, a well thought try/catch blocks could save the day.

The only thing that is left is to see how it is implemented. You can have a look at the utl_try.h and associated test cases ut_try.c file or read a more detailed post that explain the inner working mechanism of this flavour of try/catch.


0 comments :

Post a Comment