Error handling in C programming on Linux

By | 08/09/2012

While writing C code for Linux programs, it’s very important to put in place proper error handling mechanism as it saves precious time while debugging any problem with the same code. Mostly developers write code that contains broken error handling and this causes them to invest hours while debugging a complex code. In this example we will discuss in detail how to perform error handling in C.

Error handling in C

One of the my personal experience was when I was debugging a problem in a code from a vendor. The code was crashing somewhere under a particular scenario. The code was running on an embedded device and I had no debugger to use. All I was left with was to add debug prints in the code and look out for logs. I spent 2 hours debugging this way and finally reached up to a point where the return address from a function call was not being checked for NULL while it was actually coming out to be NULL under a particular scenario. And since the same pointer (holding the NULL address for this case) was being used in the next line, so there was a crash at that point. Now you can easily imagine how small the problem was but lack of proper error handling made it a nightmare for me to debug.

In this article, we will discuss some very basic error handling that can be put in place while developing Linux programs in C language.

Check the return type

Return types from functions are of utmost importance. These return types describe how the function executed. Whether it passed, failed or some thing else happened. Especially in case of a function returning an address, it becomes mandatory to check whether an address was returned or a NULL was returned. Otherwise this could lead to crashes that become nightmare to debug. Usually Linux system calls return -1 on failure but that’s not true for all. So one should be aware of the return types of a function and should handle these return types accordingly. A sample error handling check for a function could be something like :

...
...
...
char *ptr = NULL;
ptr = some_function_returning_an_address();
if(NULL == ptr)
{
    /**
      Handle the error properly here.
      Log a message here or increment 
      a counter or whatever. If this 
      error is critical then return 
      from here.
     **/
}
...
...
...

Use the errno variable

The errno variable is a global variable that most of the Linux system calls (and some library functions) use to indicate an error by setting its value to a positive number. A check on the value of this variable strengthens the error handling in code. To better understand its usage, consider the following example :

#include<stdio.h>
#include<errno.h>

int main(void)
{
    FILE *fd = NULL;

    // Reset errno to zero before calling fopen().
    errno = 0;

    // Lets open a file that does not exist   
    fd = fopen("Linux.txt","r");
    if(errno || (NULL == fd))
    {
        printf("\n fopen() failed\n");
    }

    return 0;
}

So we see that we used the function fopen() and checked the errno variable after calling it. If errno comes out to be any non zero value then this means that fopen failed. A point worth noting here is about initializing errno with zero before calling to a function that sets it. It’s always done because if a call to a function A sets errno then it’s not always ensured that the next call to a function B would reset it to zero in a case where call to function B succeeds. So in such a case, the errno variable would still be holding a non zero value (from the failure of function A) and could be misinterpreted as a result of failure of function B. So it is always advisable to initialize errno to zero before any function call that sets it.

Please note that errno variable is thread safe ie it is safe to use this variable in a multi threaded environment.

The strerror() and perror() functions

After going through previous section (where we explained about errno variable), one may ask as to what the potential advantage of using errno variable when the return type is capable of the same thing. Well, the question is valid one, so lets now discuss the functions strerror() and perror() without which the usage of errno variable is not complete.

Whenever a function fails and sets the errno variable then what we see is a non zero value of errno. What if you are provided with a function that translates this error code into a human readable string? Yes, you guessed it right. The function strerror() and perror() do the same. Lets understand them one by one.

Here is the prototype of strerror() function from the man page :

#include <string.h>
char *strerror(int errnum);

The strerror() function returns a pointer to a string that describes the error code passed in the argument errnum. This string must not be modified by the application, but may be modified by a subsequent call to perror(3) or strerror(). No library function will modify this string.

So we see that this function accepts the errno value and returns pointer to a string that provides description on errno value in human readable form. Here is an example of this function :

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main(void)
{
    FILE *fd = NULL;

    // Reset errno to zero before calling fopen().
    errno = 0;

    // Lets open a file that does not exist   
    fd = fopen("Linux.txt","r");
    if(errno || (NULL == fd))
    {
        // Use strerror to display the error string
        printf("\nThe function fopen failed due to : [%s]\n",(char*)strerror(errno));
        return -1;
    }

    return 0;
}

Now, if we execute the code above :

$ ./errno

The function fopen failed due to : [No such file or directory]

So we see that the function strerror() returned a human readable string that clearly specified that fopen() failed as there is no such file or directory.

One problem with strerror() is that it uses a common buffer to store the error message that is overwritten by subsequent calls to functions that fail and set errno. This could be a problem mostly in the multi threaded applications where a thread context switch could easily flush off the previously stored string that was to be used by the thread that was earlier under execution. To sort this problem out, there is a thread safe variant of this function known as strerror_r(). This function accepts a buffer as an argument that it fills with the error string and hence eliminates the problems faced in case of strerror() function.

To learn more on strerror() and strerror_r() go through the man page of these functions.

Now lets shift the focus on perror() function. Here is an excerpt of the description of this function from its man page :

#include <stdio.h>
void perror(const char *s);

The routine perror() produces a message on the standard error output, describing the last error encountered during a call to a system or library function. First (if s is not NULL and *s is not a null byte (‘\0′)) the argument string s is printed, followed by a colon and a blank. Then the message and a new-line.

To be of most use, the argument string should include the name of the function that incurred the error. The error number is taken from the external variable errno, which is set when errors occur but not cleared when non-erroneous calls are made.

So we see that this function is different from strerror() family of functions as it produces the output on standard error and it also accepts an argument where the developer can provide an additional string to add more meaning to the error log that is to be printed by perror() function. Usually developers pass the name of function as argument so that name of the function gets printed along with the error string.

Here is an example of perror() function :

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main(void)
{
    FILE *fd = NULL;

    // Reset errno to zero before calling fopen().
    errno = 0;

    // Lets open a file that does not exist   
    fd = fopen("Linux.txt","r");
    if(errno || (NULL == fd))
    {
        // Use strerror to display the error string
        perror("The function fopen failed due to");
        return -1;
    }

    return 0;
}

So we see that in the code above, we have used the perror() function and passed our own string as an argument to it. Now lets run this code, following is the output :

$ ./errno
The function fopen failed due to: No such file or directory

So we see that the string supplied to the perror() function was printed first followed by a colon and then finally the error string corresponding to the error that occurred while processing fopen() function.

The man pages or the documentation of a function

This is a very healthy habit to first go through the complete help/documentation/man-page of a function. This not only provides an overview of the function but also provides detail information on how and why a particular function would fail, what it returns in different scenarios etc. More the knowledge of a function, better the error handling you can put around it in code.

Leave a Reply

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