Elegant error checking

10,993

Solution 1

Another macro-based approach which you can use to mitigate the shortcomings in C fairly easily:

#define CHECK(x) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    return /* or throw or whatever */; \
  } \
} while (0)

Then to invoke it you have:

CHECK(doSomething1());
CHECK(doSomething2());
// etc.

For bonus points you could easily extend the CHECK macro to take a second argument y that is what to do on failure:

#define CHECK(x, y) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    y; \
  } \
} while (0)

// We're returning a different error code
CHECK(someFunction1(foo), return someErrorCode);
// We're actually calling it from C++ and can throw an exception
CHECK(someFunction2(foo), throw SomeException("someFunction2 failed")):

Solution 2

Usually, in C, one uses goto for error handling:

int foo()
{
    if (Function1() == ERROR_CODE) goto error;
    ...
    struct bar *x = acquire_structure;
    ...
    if (Function2() == ERROR_CODE) goto error0;
    ...

    release_structure(x);
    return 0;

error0:
    release_structure(x);

error:
    return -1;
}

This can be improved with macros and more clever instruction flow (to avoid repeating cleanup code), but I hope you see the point.

Solution 3

I think you should look at exceptions and exception handling. http://www.cplusplus.com/doc/tutorial/exceptions/

try{    
    callToUnderlyingLibrary1();
    callToUnderlyingLibrary2();
    callToUnderlyingLibrary3();
}catch(exception& e)
    //Handle exception
}

your library functions can throw exceptions if there is an error

Solution 4

Here is a proposition, you may or may not like it:

  • make your functions return 0 on failure, something else on success
  • if something fails in your functions, have them set a global (or static) variable to the error code (like errno)
  • create a die() function that prints the error depending of the error code (or whatever you want it to do)
  • call your functions with do_something(foo, bar) || die("Argh...");

Solution 5

I prefer a variant of Alexandra C.'s goto-approach:

int foo()
{
    int rv = 0;
    struct bar *x = NULL;
    struct bar *y = NULL;
    rv = Function1();
    if (rv != OK){
      goto error;
    }
    //...
    x = acquire_structure();
    if (x==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...
    rv = Function2();
    if (rv != OK){
      goto error;
    }
    //...
    y = acquire_structure();
    if (y==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...

    rv = release_structure(x);
    x = NULL;
    if (rv != OK){
      goto error;
    }
    rv = release_structure(y);
    y = NULL;
    if (rv != OK){
      goto error;
    }
    return OK;

error:
    if (x!=NULL){
      release_structure(x);
    }
    return rv;
}

When you use multiple goto-destinations, it is easy to mix them up. Or perhaps you move the initialization of a variable, but forget to update the gotos. And it can be very difficult to test all ways a C-method can fail.

I prefer having a single goto-destination that performs all the cleanup. I find that makes it easier to avoid mistakes.

Share:
10,993

Related videos on Youtube

Ren
Author by

Ren

Updated on June 17, 2022

Comments

  • Ren
    Ren almost 2 years

    Our code (in a simple library implementation) is beginning to look like this:

    err = callToUnderlyingLibrary1();
    if (err!=0) {
    printf ("blah %d\n", err);
    ...
    }
    
    err = callToUnderlyingLibrary2();
    if (err!=0) {
    printf ("blah %d\n", err);
    ...
    }
    
    err = callToUnderlyingLibrary3();
    if (err!=0) {
    printf ("blah %d\n", err);
    ...
    }
    

    This is cumbersome and ugly. Is there a better way to do this ? Perhaps using the C preprocessor ? I was thinking something like:

    CHECK callToUnderlyingLibrary1();
    CHECK callToUnderlyingLibrary2();
    CHECK callToUnderlyingLibrary3();
    

    where the CHECK macro invokes the function and does the rudimentary error checking.

    Are there preferred idiomatic ways of handling this ?

    • Oliver Charlesworth
      Oliver Charlesworth over 12 years
      You should probably pick either C or C++, not both. In C++, the canonical answer will involve exceptions. In C, it will not.
    • Ren
      Ren over 12 years
      Good idea. Let me remove the C++ tag.
    • hari
      hari over 12 years
      I wish I could give more UP votes!!!
    • Roddy
      Roddy over 12 years
      As you originally tagged it C and C++, is it a fair assumption that this involves a C library being invoked from C++ code??
    • Ren
      Ren over 12 years
      @Roddy: Yes. But our code is really a very thin layer on top of the C library, so it is really C for all practical purposes.
    • Mouna Cheikhna
      Mouna Cheikhna over 12 years
      + 1 for wanting a better way to do this :)
  • Ren
    Ren over 12 years
    Yes, that would have been nice, but unfortunately, it's not an option for us. We are tied to using a certain underlying library, which does not throw exceptions.
  • Alexandre C.
    Alexandre C. over 12 years
    I won't downvote since the C++ tag has been removed after your answer. But the question deals with C, which has no try/catch construct.
  • Ren
    Ren over 12 years
    Right. My fault for including the C++ tag to begin with. Please don't downvote this answer, which would apply correctly to C++.
  • Roddy
    Roddy over 12 years
    @Ren: Can you make a C++ wrapper for the library, then? Usability of most C libraries can be enormously improved for C++ with wrapper classes and a sprinkling of RAII and exceptions.
  • Roddy
    Roddy over 12 years
    Gotos in Macros :-) Thanks for reminding me why I program in C++ when possible!
  • Alexandre C.
    Alexandre C. over 12 years
    @Roddy: if you keep the functions short and if you have some discipline, there is nothing wrong with this approach. Gotos and macros have their use here.
  • Ren
    Ren over 12 years
    @Roddy: Noted. I think that would be a good long-term enhancement of the codebase.
  • pmg
    pmg over 12 years
    These gotos are not in macros! :)
  • Roddy
    Roddy over 12 years
    @Alexandre: yes - in C you have to live with the cards that the language has dealt you - and Macros and gotos are essential ones. But personally, I don't have the necessary disipline to stay in control of that, which is why C++ and RAII works for me :-)
  • Alexandre C.
    Alexandre C. over 12 years
    @Roddy: sure, you're right. I stay away from C when I can. Sometimes you don't have the choice of the language though.
  • Seth Carnegie
    Seth Carnegie over 12 years
    If your functions returned 0 on success, do_something(foo, bar) || die("Argh..."); would call die on success.
  • fluffy
    fluffy over 12 years
    That's pretty bad macro style that leads to all sorts of weird parse issues, not to mention you have a macro that's suddenly expecting something about variables in the enclosing scope.
  • Seth Carnegie
    Seth Carnegie over 12 years
    @fluffy What are some examples of weird parse issues?
  • luser droog
    luser droog over 12 years
    This technique tends to generate spurious compiler warnings about 'value calculated is not used' unless you wrap it (void)(...)
  • fluffy
    fluffy over 12 years
    if (someCondition) CHECK(blah); else CHECK(otherblah); - it's better to wrap a multi-statement macro in a do { ... } while (0) for this reason as it fixes pretty much all those situations.
  • Seth Carnegie
    Seth Carnegie over 12 years
    @fluffy why would if (someCondition) CHECK(blah); else CHECK(otherblah); cause problems?
  • fluffy
    fluffy over 12 years
    Because it's a multi-line expression, but CHECK is not enclosed in curly braces. Expand the macro out yourself; you end up with: if (someCondition) (err=x()); if(err){...}else (void)0; else (etc.) which doesn't even parse.
  • Seth Carnegie
    Seth Carnegie over 12 years
    @fluffy ah ok, I see what you mean.
  • hlovdal
    hlovdal about 11 years
    Gotos in macros can be quite nice, however always pass the label to jump to as a parameter to the macro. This makes the macro more flexible as well as not hiding that execution might continue somewhere other than the next line.
  • Emile Vrijdags
    Emile Vrijdags over 7 years
    what does it mean to have elegant code: quora.com/What-does-one-mean-by-elegant-code . one return statement has little value and usually stems from a misconception: softwareengineering.stackexchange.com/questions/118703/…
  • alexs973
    alexs973 over 5 years
    Why do you choose to use a macro, and not a function / inline function?
  • fluffy
    fluffy over 5 years
    @alexs973 Because in this case the thing needs to be able to return from the function that's calling it, and also the question was for C-language so exceptions are not supported.
  • Julian
    Julian about 2 years
    Is there anyway to obtain information about the function being called or its parameters?
  • fluffy
    fluffy about 2 years
    @Julian The #x parameter to fprintf includes the string version of the first parameter in the error log. What additional information are you trying to obtain? Unfortunately preprocessor macros are pretty limited in what sort of scope-level information they can get.