Error Handling in C

Discussion in 'C' started by Trinity, Dec 21, 2012.

  1. Error handling has always been dominantly vital in programming. It has become a bit more sophisticated with the object oriented languages, however even in the system programming language like C, error handling is being offered in its own simple ways.

    Introduction



    To swim deep into the error handling provided by C, first let us understand what does it mean?

    When we program, there can be errors. By errors, I don’t mean the syntax errors, which obviously are well pointed out by the compiler. Errors in C programming generally refer to the run-time unexpected behaviour due to some exceptional input value, or maybe some incorrect argument to a correct computation, etc

    To think of an example, suppose we have a C function to do division i.e. it divides one number by another. It is so simple, here is how it looks like
    Code:
    float divide(int x, int y)
    {
        float result;
        result = x/y;
        return result;
    }
    
    It seems to work perfectly fine, and it will for most of the cases, no doubt about that. However, what if we call the function ‘divide’ with following two parameters
    Code:
    divide(5, 0);
    
    GCC throws a fatal error:
    Code:
    Floating point exception (core dumped)
    
    However, this behaviour may vary from compiler to compiler. Hence, it is highly recommended, that the programmer take the onus to handle this error in one’s own sweet way. This is how it is done in the above example
    Code:
    float divide(int x, int y)
    {
        float result = 0.0;
       if (y == 0)
    {
        printf(“Division by Zero\n”);
    }
    else
    {
        result = x/y;
    }
        return result;
    }
    

    Standard Error Variables and Functions



    In the above example, we just printed off our error statement and returned a zero in case of error condition. However, does that mean, for every error condition, we need to check for the conditions and then handle the anticipated errors.

    C provides a few standard variables and functions to do the same task, and even assist in identifying the errors if the programmer somehow fails to do so.

    Variable errno

    A standard error variable ‘errno’, which is assigned the error code, whenever a silent error occurs. By silent error, I mean an error which is not critical enough to crash the program or abort it. One needs to include following header, where it is defined.
    Code:
    #include <errno.h>
    
    To view what error code corresponds to which error, one can look into ‘errno.h’. A zero value of ‘errno’ means there is no error.

    To understand, let us write a program to write into a file.
    Code:
    #include <stdio.h>
    #include <errno.h>
    
    int main()
    {
        FILE *fp = NULL;
    
        fp = fopen("rofile", "w+");
        if (fp == NULL)
        {
            printf("errno is %d\n", errno);
        }
        else
        {
            fprintf(fp, "Able to write\n");
        }
    
        return 0;
    }
    
    Compiling the source and verifying in the current directory if ‘rofile’ is present. Further running the obtained executable.
    Code:
    $ ls
    filerr.c  rofile
    $ gcc filerr.c -Wall -o filerr
    $ ./filerr
    errno is 13
    
    Oh, we got an error! We had verified the existence of the file, before running our executable. Then what is the error? Let is check through the headers to determine what does the error code ‘13’ mean?
    In my case, file is /usr/include/asm-generic/errno-base.h
    Code:
    #define EACCES          13      /* Permission denied */
    
    So, we were not allowed to do the operation ‘write’ on the file. I need to check the permissions of the file now.
    Code:
    -r--r--r-- 1 rupali rupali   23 Dec 20 20:19 rofile
    
    So, it is read-only file, and hence trying to open the file in the ‘write’ mode causing this error.

    Hence, we just went through a typical debug experience how the standard variable ‘errno’ helped us finding the root-cause of the issue pretty easily.

    To run the same situation example on your end, you just need to create a file ‘rofile’ and run following command on it.
    Code:
    $chmod 444 rofile
    
    A good point to note is, the ‘errno’, gets overwritten whenever an error occurs. Hence, in a series of errors, errno would store the error code of the last error.

    Function perror()

    This function adds as an leverage to determine, the error through its description string, rather than the error code. The protocol of perror(), taken from its man page
    Code:
    void perror(const char *s);
    
    It prints a message with description of the last error occured. It also takes our message as an input parameter and displays it.

    Modifying our same example source discussed above to use perror()
    Code:
    #include <stdio.h>
    #include <errno.h>
    
    int main()
    {
        FILE *fp = NULL;
    
        fp = fopen("rofile", "w+");
        if (fp == NULL)
        {
            printf("errno is %d\n", errno);
            perror("A File Error");
        }
        else
        {
            fprintf(fp, "Able to write\n");
        }
    
        return 0;
    }
    
    Checkout what we got as our output:
    Code:
    errno is 13
    A File Error: Permission denied
    
    See, it printed the same description message “Permission denied”, which we determined from the header file - errno.h.

    Error Handling Techniques



    There are broadly simple two techniques of error handling in C. One, is to determine the error situation, and then add the error handling code to safely handle the error. Like in the above examples, all the errors are handled just after the error might have been occurred. And in case, the error is fatal, and cannot be moved further with, exit from the program.

    This way, in no way errors cause any propagation buggy effects. However, this becomes inefficient in cases where there is a lot of error handling. It leads to multiple exit points in the program, and might also lead to redundancy of the cleanup code, wherever it is being called, at the exit point.

    Hence, we have a second technique of error handling, using ‘goto’ statements. This is widely used in professional sources, where they have huge code infrastructure. To avoid the multiple exit points, and cleanup code redundancy, we can use a ‘goto’ to jump to a certain point, which leads to a common exit point, skipping the not desired flow of code in case of errors.

    Following code snippet explains how does it look like.
    Code:
    int function(..................)
    {
    .....
    .
    ..
    if (errno)
    {
        goto ErrorExit;
    }
    
    ....
    ..
    if (errno)
    {
        goto ErrorExit;
    }
    ....
    ..
    if (errno)
    {
        goto ErrorExit;
    }
    ....
    ..
    if (errno)
    {
        goto ErrorExit;
    }
    
    Errorexit:
        perror(“Function Error!”);
        cleanup();
    }
    
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice