Error handling paradigm for C programs


Error handling is an indicator of the programmer's professionalism. It is very important to do proper error handling to achieve robust performance from your C program. In this article, I explain a paradigm, or method for managing error handling in C code.


Errors

An error is a possible outcome of the execution of a particular statement. In case the statement fails, error codes may be returned if the statement is a function call. In case of memory allocations, the error corresponds to the failure of the system to allocate the requested memory.


How to check for errors


Function calls

The common wisdom is to reserve the return argument of function calls for communicating errors and notifications to the calling/parent function. If not, one of the function arguments should be used for this purpose. As a rule thus, functions should always communicate errors and notifications to calling functions through integer numbers called codes.

Error codes should be negative integers. To check for an error, the calling function examines the return value. If the return value is negative, then error handling takes place.


Memory allocation

Memory allocation generally do not return error codes. Functions such as malloc return a pointer to a memory area if the memory allocation succeeds, or a null pointer if the allocation fails.

To check for memory allocation errors, the calling function must check whether the returned pointer is null or not. In case of a memory allocation error, the calling function should cancel further processing that depends on the failed memory allocation.


How to handle errors

It is important to adopt a clear strategy for handling errors, this helps in making error handling a systematic reflex while you write your code. To this end, I propose the following approach based on two elements :


Wrapper functions for system calls

System calls (malloc, open, close, write, etc.) are most likely the functions that you use the most in your code. To avoid performing error checking each time you use one of these functions, I find it much easier to write wrapper functions that do the system call and error handling for you. For example, this is a wrapper function for malloc

void *
Malloc(size_t size)
{
        void    *ptr;
 
        if ( (ptr = malloc(size)) == NULL)
                err_sys("SYS #  malloc error");
               /* err_sys logs an error using syslog then terminates the current process */
 
        return(ptr);
}

The function Malloc above allows you to allocate memory and do error handling and logging in the same time.


Goto statements in functions

Error handling is one situation where the goto statement is useful. To perform cleanup after an error, it is much easier to jump to a set of statements at the end of the function that take care of freeing resources and failing in a gracious way. This avoid you writing the error handling code each time a function call or memory allocation fails. Note that goto statements can also be used to free resources before the function returns.

This is an example of using the goto statement in a function to perform error handling and clean-up.

int
my_function(void)
{
 
        struct something *item1;
        struct somethingelse *item2;
 
        item1 = allocate_thing(arguments);
        item2 = allocate_thing2(arguments2);
 
        if (!item2 || !item2)
                goto fail;
 
        err = do_stuff(item1, item2);
 
        if (err) /* Assuming that do_stuff returns non 0 in case of error */
                goto fail;
        else
                return 0;
 
fail:
        release_thing(item1);
        release_thing2(item2);
        return err;
}

Safe coding.



Labels: , Wireless Internet Security Coding Network Monitoring