Error handling conventions in C programming languages

EDIT February 22, 2014: You’ll find the code for the functions mentioned in this post (and related ones) in TECommon

Programmers have many options available to them when it comes to error handling.  A very common convention among C programmers is to make a function return a non-zero value if an error has occurred.  This post is about what to do with that non-zero value.

Let’s start with a simple example:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4 
    5     err = SecKeychainSetUserInteractionAllowed(TRUE);
    6 
    7     if ( err ) {
    8         log_err("couldn’t enable keychain user interaction");
    9         return err;
   10     }
   11 
   12     err = SecKeychainUnlock(gKeychain, 0, NULL, FALSE);
   13 
   14     if ( err ) {
   15         log_err("couldn’t unlock keychain");
   16     }
   17     else {
   18         err = SecKeychainAddCallback(MyKeychainCallback, kSecEveryEventMask, NULL);
   19         if ( err ) {
   20             log_err("couldn’t set callback for keychain");
   21         }
   22     }
   23 
   24     if ( err == 0 ) {
   25         doThatFancyThingYouDo();
   26     }
   27 
   28     return err;
   29 }

This example demonstrates three common error handling techniques that I’ve encountered in the wild:

  1. Return immediately (line 9)
  2. Building nested if-else clauses (lines 14-22)
  3. Repeatedly checking error status (line 24)

This is just a simple, short example, but these error checking patterns can really add up in more complicated code, making it unwieldy and unnecessarily complex, not to mention a pain to maintain and debug. There’s also the nuisance of having to write a custom error message for each situation as well, and most of the time developers tend to just avoid doing that altogether, making it difficult to troubleshoot problems when they occur on a remote system.

To get around these problems developers often use macro’s with goto’s. Here’s one technique that I’ve seen:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4     err = SecKeychainSetUserInteractionAllowed(TRUE);
    5     require_noerr(err, fail_label);
    6     err = SecKeychainUnlock(gKeychain, 0, NULL, FALSE);
    7     require_noerr(err, fail_label);
    8     err = SecKeychainAddCallback(MyKeychainCallback, kSecEveryEventMask, NULL);
    9     require_noerr(err, fail_label);
   10     doThatFancyThingYouDo();
   11 fail_label:
   12     return err;
   13 }

Now that’s certainly an improvement, we went from 29 lines down to 13, and the code is much more readable. That’s pretty good, but I think we can do better, here’s my version:

    1 OSStatus initKeychainAccess()
    2 {
    3     OSStatus err;
    4     DO_FAILABLE(err, SecKeychainSetUserInteractionAllowed, TRUE);
    5     DO_FAILABLE(err, SecKeychainUnlock, gKeychain, 0, NULL, FALSE);
    6     DO_FAILABLE(err, SecKeychainAddCallback, MyKeychainCallback, kSecEveryEventMask, NULL);
    7     doThatFancyThingYouDo();
    8 fail_label:
    9     return err;
   10 }

In the event of an error, you’ll get all of the important information that you need to pinpoint exactly what happened: the function that failed, the error code, and the line number. Here are the definitions for DO_FAILABLE and a few variants thereof:

#define DO_FAILABLE(_errVar, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        log_err(#_func ":%d returned: %d\n", __LINE__, (int)_errVar); \
        goto fail_label; \
    } \
} while (0)

// useful when the error code isn't the return value
// ex: DO_FAILABLE_SUB(err, errno, setuid, getuid());
#define DO_FAILABLE_SUB(_errVar, _subst, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        _errVar = _subst; \
        log_err(#_func ":%d resulted in: %d\n", __LINE__, (int)_errVar); \
        goto fail_label; \
    } \
} while (0)

// just note that an error occurred, but don't do anything about it
#define FAILABLE(_errVar, _func, args...) do { \
    if ( (_errVar = _func(args)) != 0 ) { \
        log_err(#_func ":%d returned: %d\n", __LINE__, (int)_errVar); \
    } \
} while (0)

This cuts down on the number of lines of code by implicitly assuming that fail_label exists (more often than not, a single fail label is enough). Also note the cast to int, this is necessary because sometimes your error value might be stored in a type that will cause gcc to give a warning (e.g. if you have it enabled via -Wall) because of a mismatch between the type and the %d in the printf statement.

Another nice thing about these macros is that they’re very easy to adopt, oftentimes you could easily throw them into your code via a regex find&replace. You simply prepend DO_FAILABLE( in front of the error assignment, convert the equals sign into a comma, and replace the first open parenthesis in the function call with another comma. Then add a fail_label somewhere.

The real fun comes afterward, when you get to delete hundreds of lines of unnecessary error-checking code. 🙂

One thought on “Error handling conventions in C programming languages

  1. Reply

    Giedrius Statkevičius

    Ugh, I feel that these macros which generate code completely hide the real purpose. I would rather write them explicitly. It is 2017 – you don’t have to squeeze your functions so much so that they would be as small as possible. You can just write everything out, lay it bare. It is much more understandable this way because the person reading the code does not have to keep this abstraction in mind. I wrote a short article about a similar topic: http://giedrius.blog/2017/10/22/making-unwinding-functions-in-c-simple-do-not-be-afraid-of-using-gotos/. Please check it out and let me know what you think 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *