Avoid repetition when handling C errors

I often write code that ends with long sequences 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 am looking for a cleaner way to write this, to some extent avoid repeated identical checks. So far I have written the macro ERROR_OR , which works like

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

which allows the source 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 (in my opinion) is a little cleaner. This is also less clear, since the macro function ERROR_PRESERVE not obvious unless you read its documentation and / or implementation. It also does not solve the problem of repetition, it simply simplifies the recording of all (now implicit) checks in one line.

That I really would like to rewrite all this as it would be:

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

The hypothetical macro ERROR_SHORT_CIRCUIT will be

  • Take a variable number of expressions in an argument list
  • Rate each expression in order
  • If each expression evaluates to zero, first evaluate its value
  • If any expression evaluates to nonzero, terminate immediately and evaluate the value of this last expression

The last condition is that my short circuit diverges from the direct use of the || because it will be evaluated at 1 instead of the error value.

My initial attempt to spell this:

 #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 does not handle its base register (when __VA_ARGS__ is the only value)
  • C does not support recursive macros

I looked through a few recursive hacks macros , but I don’t like to use this degree of preprocessing magic - there is too much space for something wrong. I also considered using real (possibly variational) functions, but that would require

  • short circuit failure behavior
  • passing functions as pointers and therefore normalizing their signatures

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

I am interested in hearing tips on the best way to handle this. I am open to different approaches, but my ultimate goal is to avoid repetition without compromising readability.

(I think it’s obvious that I envy the behavior of the || operator in languages ​​like Ruby).

+5
source share
2 answers

I would 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 is fully defined because before each statement || there are points of sequence. No macro really needed. This only runs into problems when allocating resources or other operations between function calls, but it is different from what the code example shows. At least 90% of the battle was created by a sequence of do_something_or_other() functions that simplify error sequence handling.

+4
source

Another option:

 int error = 0; do { // Note: extra parens suppress assignment-as-conditional warning if ((error = do_something())) break; if ((error = do_something_else())) break; if ((error = do_yet_another_thing())) break; error = do_a_final_thing(); } while (0); return error; 
+2
source

Source: https://habr.com/ru/post/1271618/


All Articles