Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling error codes from multiple libraries in C

Very often, I have to use multiple libraries that handle errors differently or define their own enums for errors. This makes it difficult to write functions that might have to deal with errors from different sources, and then return its own error code. For example:

int do_foo_and_bar()
{
    int err;
    if ((err = libfoo_do_something()) < 0) {
        // return err and indication that it was caused by foo
    }
    if ((err = libbar_do_something()) < 0) {
        // return err and indication that it was caused by bar
    }
    // ...
    return 0;
}

I've thought of two possible solutions:

  • Create my own list of error codes and translate these error codes to new ones, using functions like int translate_foo_error(int err), and I would write my own string representations for each error.
  • Create a struct my_error that holds both an enum identifying the library and the error code. The translation to string would be delegated to an appropriate function for each library.

This seems like a problem that would come up very often, so I'm curious, how is this usually handled? It seems like the former is what most libraries do, but the latter is less work and plays on the tools already provided. It doesn't help that most tutorials just print a message to stderr and exit on any error. I'd rather have each function indicate what went wrong, and the caller can decide from that how to handle it.

like image 678
James Hagborg Avatar asked Jul 08 '14 05:07

James Hagborg


People also ask

Does C have error handling?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

Which library has functions for error handling?

C language uses the following functions to represent error messages associated with errno: perror() : returns the string passed to it along with the textual represention of the current errno value. strerror() is defined in string. h library.


1 Answers

The answer is, it depends on your code's constraints.

collectd prints to standard error then bails if it hits a fatal error.

OpenGL will set some shared state that you can query. Ignoring this error often results in undefined behavior.

Collectd has lots of threading concerns, and most errors can't be fixed or recovered from by the program. For example, if a plugin depends on some library, and a call to that library fails, the plugin knows the most about how to recover from that error. Bubbling that error up isn't helpful as collectd core will never know about plugin N+1

On the other hand, OpenGL applications are usually responsible for any errors encountered and can attempt to correct errors. Such as, if they are attempting to compile a shader, but might have a special one for a specific vendor or platform.

Based on your program's design, consider the following:

  1. Will bubbling up the error allow you to make a better decision?
    • Does the caller know about your implementation? Should they?
  2. Are the likely errors correctable by your application?
    • E.G. If you can't open a socket or file, you can retry or fail, not much else.
  3. Are there constraints around global state if you make a GetLastError() function?

Update:

Going with the global state option you might have something like this:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

char* last_err = NULL;

void set_err(char* error_message) {
    if(last_err)
        free(last_err);
    /* Make a deep copy to be safe. 
     * The error string might be dynamically allocated by an external library. 
     * We can't know for sure where it came from. 
     */
    last_err = strdup(error_message); 
}

int can_sqrt(int a) {
    if(a < 0) {
        set_err("We can't take the square root of a negative number");
        return 0;
    }
    return 1;
}

int main(int argc, char* argv[]) {
    int i = 1;
    for(i = 1; i < argc; i++) {
      int square = atoi(argv[i]);
      if(can_sqrt(square)) {
        fprintf(stdout, "the square root of %d is: %.0f\n", square, sqrt(square));
      } else {
        fprintf(stderr, "%s\n", last_err);
      }
    }
    return 0;
}

Running the above program

$ ./a.out -1 2 -4 0 -6 4
We can't take the square root of a negative number
the square root of 2 is: 1
We can't take the square root of a negative number
the square root of 0 is: 0
We can't take the square root of a negative number
the square root of 4 is: 2
like image 148
EnabrenTane Avatar answered Nov 14 '22 23:11

EnabrenTane