tags:

views:

125

answers:

9

How does one deal with error return of a routine in C, when function calls go deep?

Since C does not provide an exception throw mechanism, we have to check return values for each function. For example, the "a" routine may be called by "b", and "b" may called by many other routines, so if "a" returns an error, we then have to check it in "b" and all other routines calling "b".

It can make the code complicated if "a" is a very basic routine. Is there any solution for such problem? Actually, here I want to get a quick return path if such kind error happens, so we only need to deal with this error in one place.

+2  A: 

You make a list of error_codes (I use enum for that) and use them "flat" in all your app. So if b calls a, and get one of the error codes, you can decide if you go on, or return back the original error code.

The user/programmer should have a list of all error codes...

Dani
He still needs to check most/all function calls to ensure everything went smoothly.He sounds like he wants a way around that. But without writing some kind of clever hack (eg. setjmp() or trying to manually unwind the stack) he's SOL.
Adam Luchjenbroers
Use "flat" error code in all source is good solution for code maintain.
arsane
@Adam - This is too advanced for normal users to use as error handling methods. basically - yes - you need to check everything, using a flat error code list - you can sometimes move the error upwards, without checking (if res != S_OK....)
Dani
+1  A: 

AFAIK C is a structural programming language.

If this is the problem, the same would apply to RTL functions like fopen, fscanf etc ...

So I guess it is better to propagate errors.

Alphaneo
+3  A: 

I'm afraid that's the way it is. Without exceptions, you have to check the return value of every function in the call chain.

Philippe Leybaert
+4  A: 

There are several strategies, but the one I find the most useful is that every function returns zero on success and nonzero for an error, where the specific value indicates the specific error.

This combined with early return logic actually makes the functions quite easy to read:

int
func (int param)
{
    int rc;
    rc = func2 (param);
    if (rc)
        return rc;

    rc = func3 (param);
    if (rc)
        return rc;

    // do something else
    return 0;
}
wallyk
A: 

You could use a macro.

#define FAIL_FUNC( funcname, ... )    if ( !funcname( _VA_ARGS_ )  ) \
                                           return false;

This way you maintain the same system but without having to write the same code each time ...

Goz
That's fine until you need to release the allocated memory or the opened file descriptor, or ... then the early return is a resource leak.
Jonathan Leffler
+4  A: 

You can use setjmp() and longjmp() to simulate exceptions in C.

http://en.wikipedia.org/wiki/Setjmp.h

Murali VP
Just be aware that setjmp()/longjmp() are *very* primitive exception handling mechanisms and not very well structured. They're useful, but compared to `try {...} catch() {...}` they're a bit messy.
John Bode
+3  A: 

In the general case, no. You'll want to make sure your function calls worked as expected. Return codes are your main mechanism for ensuring this (although setting a global error number or error flag may also be appropriate, depending on context - not that it simplifies things much).

Adopting one of the techniques others have suggested should allow you to make your error checking uniform and easier to read. This will go a long way towards keeping things maintainable.

For some basic functions though, the odds of failure may be low enough not to bother, eg.

int sum(int a, int b) {
  return a + b;
}

really doesn't need to be checked. But that system call to create a new window really should be.

Adam Luchjenbroers
Beware: integer overflow bugs exist because simple code like that in `sum()` is not error checked. They can be exploited much the same as a buffer overflow bug can be. That said, it is best if you write functions that do not have failure modes - but you can't avoid it much of the time.
Jonathan Leffler
A valid point - if you try hard enough you can break anything. But at a certain point you have to make a decision regarding robustness vs. maintainability. That said, the error checking could probably be embedded in the function in this case. You'd just need to decide what an appropriate return value is in this case (MAX_INT?).
Adam Luchjenbroers
@Adam, I know it's only an example, but a function to add two numbers is useless *unless* it employs a robust error checking facility since the language already includes an operator to sum two addends. Most basic functions will have this same problem. So we're left only with interesting functions to design/code. In that case cramming return codes into actual return values is a more complicated approach than necessary since it counfounds the purpose of the variable. Trivial enough to pass a pointer as a parameter that will either hold the computed value or error data.
mcl
Incorrect, a function that simply adds two integers is also useful as a trivial example used to explain a point. :P.
Adam Luchjenbroers
+1  A: 

The best way is to design functions, whenever possible, in ways that cannot fail. This is impossible if they do I/O or memory allocation or other things with side effects, so avoid those. For example, instead of having a function that allocates memory and copies a string, have a function that gets pre-allocated memory to which it copies a string. Or you might have only one place where I/O happens, the rest of the program just manipulates data in memory.

Alternatively, you may decide that certain kinds of errors warrant killing the process. For example, if you're out of memory, it is hard to recover from that, so you might as well crash. (But do that in a way that is user-friendly: checkpoint relevant data to disk continuously so the user may recover.) This way, functions can pretend they never fail.

The setjmp suggestion Murali VP is also worth checking out.

Lars Wirzenius
A: 

There is a somewhat ugly workaround, which I do not recommend. It basically violates the way C is done, introduces several bad coding practices, and generally reduces readability.

error.c:

int g_error = 0;

error.h:

extern int g_error;

enum {
    E_FOO = 1,
    E_BAR = 2,
    E_BAZ = 4
} ERRORCODE;

main.c:

#include "error.h"

void foo() {
    // ...
    if ( errorcondition ) {
        g_error |= E_FOO;
        return;
    }
}

int main() {
    g_error = 0;
    foo();
    if ( g_error != 0 ) {
        // handling the error
    }
    return 0;
}
DevSolar