Question

In languages and/or libraries which do not support exceptions, many/almost all functions return a value indicating success or failure of their operation - the best-known example being perhaps UN*X system calls such as open() or chdir(), or some libc functions.

Anyway, when I write C code, it very often ends up looking like this:

int retval;
...
retval = my_function(arg1, arg2);
if (retval != SUCCESS_VALUE) { do_something(); }

retval = my_other_function(arg1, arg2);
if (retval != SUCCESS_VALUE) { do_something_else(); }

Now, what I would like is to just not save the retval anywhere and have errors thrown in exceptions, but I can't have that. What's the next best thing? I know that there's no real solution to this problem, but I would still like to do something.

Some ideas:

  • Try to live with assert()'s (but that's not for production code which can't afford to just die).
  • Wrap function calls with a macro or a return-value-checking function, e.g. ensure_success(my_function(args) or ensure_success(my_other_function(args),my_error_handler,error_handler_args).

Is there any other practice in this matter I might prefer?

Edit:

  • Yes, I'm writing C code. I respect your opinion that I should try to avoid writing in C altogether, but that's really not constructive. This is not a language-wars question, please don't make it one.
  • I'm not asking for opinions on what is the best thing to do, I just want additional possibilities. (I'll pick one I like, other people might pick something else.)
Was it helpful?

Solution

Try to approach this problem with a sort of scientific curiosity. There are many that claim that C's approach to error handling leads to programmers being more aware of error conditions, paying more attention to errors and where/how they should be treated. Just consider this an exercise (if a bit tedious) of awareness, like meditation :)

Don't fight it. Solve this as much as possible in the spirit of C and your view of things will expand slightly.

Check out this article on C error handling mantra: http://tratt.net/laurie/tech_articles/articles/how_can_c_programs_be_so_reliable

One general answer to your general question is: try to make functions as small as possible so that you can directly return from them on errors. This approach is good in all languages. The rest is an exercise in structuring code.

OTHER TIPS

There are many ways to achieve better error handling. It all depends on what you are trying to do. To implement extensive error handling routines just to call a few functions isn't going to be worth the fuss. In larger code bases, you can consider it.

Better error handling is most often achieved by adding more abstraction layers. For example, if you have these functions

int func1 (int arg1, int arg2)
{
  return arg1 == arg2;
}

int func12 (int arg1, int arg2)
{
  return arg1 - arg2;
}

and some error handler function:

void err_handler_func1 (int err_code)
{
  if(err_code != 0)
  {
    halt_and_catch_fire();
  }
}

then you could group functions and error handlers together. Either by making a struct based data type containing one function and one error handler, and then make an array of such structs. Or by making individual, related arrays accessed with an index:

typedef void(*func_t)(int, int);
typedef void(*err_handler_t)(int);

typedef enum
{
  FUNC1,
  FUNC2,
  ...
  FUNC_N // not a function name, but the number of items in the enum
} func_name_t;

const func_t function [FUNC_N] =
{
  func1,
  func2,
  ...
};

const err_handler_t err_handler [FUNC_N] = 
{
  err_handler_func,
  err_handler_func,
  ...
}

once you have this, you can make wrap the function calls in a suitable abstraction layer:

void execute_function (int func_n, int arg1, int arg2)
{
  err_handler[func_n]( function[func_n](arg1, arg2 );
}

execute_function (FUNC1, 1, 2);
execute_function (FUNC2, 2, 2);

and so on.

You are facing the fact that in many solutions to problems lots of things can go wrong.

Handling this "error"-situations is part of the solution to the problem.

So my answer is either stick to solve issues where nothing (or few) can fail, or take the burden of accepting that handling error conditions is part of the solution.

So (also) designing/writing the code necessary to handle failures is an essential way to success.


As for practical advise: Seeing error handling as part of the code in whole, for error handling the same rules apply as to each and every other line of code:

  • easy to understand
  • strict enough to be efficient
  • flexible enough to get extended
  • fail save (redundant to mention in this context)
  • systematic/consistent (in terms of patterns, naming, layout)
  • documented

Possible language constructs to use:

  • breaks out of local contexts
  • (goto)*
  • (longjmp & friends to "simulate" exceptions)*
  • cleanly defined (again systematic/consistent) function declarations and return codes

* To be used with discipline in a structured way.

As with a lot of things, error handling in C requires more care than in other (higher-level) languages.

Personally, I do not believe that's necessarily a bad thing as it forces you to actually think about error conditions, the associated cotrol flow and you'll need to come up with a proper design (because if you don't, the result will probably be an unmaintainable mess).

Roughly, you can divide error conditions into fatal and non-fatal ones.

Of the non-fatal ones, there are those that are recoverable, ie you just try again or use a fallback mechanism. Those, you normally handle inline, even in languages that support exceptions.

Then, there are the ones you cannot recover from. Instead, you might want to log the failure, but in genereal just notify the caller, eg via a return code or some errno type variable. Possibly, you might need to do some cleanup within the function, where a goto might help to structure the code more cleanly.

Of the fatal ones, there are those that terminate the program, ie you just print some error message and exit() with non-zero status.

Then, there are the exceptional cases, where you just dump core (eg via abort()). Assertion failures are a subset of those, but you only should use assert() if the situation is supposed to be impossible during normal execution so your code still fails cleanly on NDEBUG builds.

A third class of fatal exceptions is those that do not terminate the whole program, but just a (possibly deeply nested) call chain. You can use longjmp() here, but you must take care to properly clean up resources like allocated memory or file descriptors, which means you need to keep track of these resources in some pools.

A bit of variadic macro magic makes it possible to come up with nice syntax for at least some of these cases, eg

_Bool do_stuff(int i);
do_stuff(i) || panic("failed to do stuff with %i", i);

In rare but happy situations you can exploit the short-circuiting nature of && and ||:

if (my_function1(arg1, arg2) == SUCCESS_VALUE 
 && my_function2(arg3) == SUCCESS_VALUE)
{
  /* Proceed */
}
else
{
  /* Cry and die. */
}

If you must differentiate between an error in function 1 and 2, this might not work so well.

Aesthetically, not super-beautiful but this is error handling after all :)

Many of the glib APIs have a GError abstraction which acts as kind of (explicit of course, this being C) exception container. Or maybe errno abstraction is a better description.

It's used as a pointer to pointer, allowing you to quickly check if a call succeeded (a new GError will be created to hold any error) and handle detailed error information at the same time.

If there was a solution to this in C, then exceptions would not have been invented. You only have two choices:

  • Move to a language that supports exceptions.
  • Cry about whatever factors forced you to use C and write C, with all it's unfixable problems.

Although, if you're using Visual C, you can use SEH as exceptions in C. It's not pretty but does work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top