Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid repetition in C error handling

I often write code which ends up being long sequences something like

int error;

error = do_something();
if (error) {
    return error;
}

error = do_something_else(with, some, args);
if (error) {
    return error;
}


error = do_something_yet_again();
if (error) {
    return error;
}

return 0;

I'm searching for a cleaner way to write this that to some extent avoids the repeated identical checks. So far, I've written an ERROR_OR macro, which works something like

#define ERROR_OR(origerr, newerr)           \
    ({                                      \
        int __error_or_origerr = (origerr); \
        (__error_or_origerr != 0)           \
                ? __error_or_origerr        \
                : (newerr);                 \
    })

which allows the original code to become something like

int error = 0;

error = ERROR_OR(error, do_something());
error = ERROR_OR(error, do_something_else(with, some, args));
error = ERROR_OR(error, do_something_yet_again());

return error;

This is (in my opinion) a little cleaner. It's also less understandable, since the function of the ERROR_PRESERVE macro isn't apparent unless you read its documentation and/or implementation. It also doesn't solve the problem of repetition, just makes it easier to write all the (now implicit) checks on a single line.

What I'd really like to re-write this all as would be the following:

return ERROR_SHORT_CIRCUIT(
    do_something(),
    do_something_else(with, some, args),
    do_something_yet_again()
);

The hypothetical ERROR_SHORT_CIRCUIT macro would

  • Take a variable number of expressions in its argument list
  • Evaluate each expression in order
  • If every expression evaluates to zero, evaluate to zero itself
  • If any expression evaluates to nonzero, immediately terminate and evaluate to the value of that last expression

This last condition is where my short-circuit diverges from a straightforward use of the || operator -- since this will evaluate to 1 instead of the error value.

My initial attempt at writing this is the following:

#define ERROR_SHORT_CIRCUIT(firsterr, ...)          \
    ({                                              \
        int __error_ss_firsterr = (firsterr);       \
        (__error_ss_firsterr != ERROR_NONE)         \
                ? __error_ss_firsterr               \
                : ERROR_SHORT_CIRCUIT(__VA_ARGS__); \
    })

This has two obvious problems:

  • It doesn't handle its base-case (when __VA_ARGS__ is a single value)
  • C doesn't support recursive macros

I've looked into some recursive macro hacks, but I dislike using that degree of pre-processor magic -- too much room for something to be subtly wrong. I've also considered using real (possibly variadic) functions, but this would require either

  • giving up the short-circuit behavior
  • passing the functions in as pointers, and therefore normalizing their signatures

and both of these seem worse than the original, explicit code.

I'm interested to hear advice on the best way to handle this. I'm open to many different approaches, but my ultimate goal is to avoid repetition without hurting readability.

(I suppose it's obvious I'm suffering some envy of the behavior of the || operator in languages like Ruby).

like image 634
Austin Glaser Avatar asked Sep 07 '17 18:09

Austin Glaser


People also ask

Does C have error handling?

C does not provide direct support for error handling (also known as exception handling). By convention, the programmer is expected to prevent errors from occurring in the first place, and test return values from functions.

What does #error do in C?

In the C Programming Language, the #error directive causes preprocessing to stop at the location where the directive is encountered. Information following the #error directive is output as a message prior to stopping preprocessing.

What is error write the technique for handling error?

Error handling helps in handling both hardware and software errors gracefully and helps execution to resume when interrupted. When it comes to error handling in software, either the programmer develops the necessary codes to handle errors or makes use of software tools to handle the errors.


1 Answers

I'd use code like:

if ((error = do_something()) != 0 ||
    (error = do_something_else(with, some, args)) != 0 ||
    (error = do_something_yet_again()) != 0)
    return error;
return 0;

It's fully defined because there are sequence points before each || operator. It doesn't really need a macro. It only runs into problems when you allocate resources or do other operations between function calls, but that is different from what your example code shows. At least 90% of the battle was creating the sequence of do_something_or_other() functions that make it easy to handle the error sequencing.

like image 198
Jonathan Leffler Avatar answered Sep 28 '22 09:09

Jonathan Leffler