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
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:
__VA_ARGS__
is a single value)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
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).
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With