On 6/29/2012 4:29 PM, rammy wrote:
> Hi,
>
> I would like to have someone comments on what's the best practice
> defining error codes in C.
> Here's what I think:
> solution A:
> using enum
> pros: type safe. better for debug (some debugger will show the name
> not only the value)
> cons: enum can not be forward declared which makes all error codes
> couples together with the error code type ( enum )
"Type safe" is too optimistic. An enum is some kind of integer
type, and integer types are freely interconvertible (as long as the
values are in range). A named enum value is just an `int' constant,
and can be stored in any integer variable (of sufficient range).
Try it yourself:
enum Fruit { APPLE, BANANA, CHERRY } fruit;
enum Motor { OTTO, DIESEL, STEAM } motor;
fruit = OTTO;
motor = BANANA;
fruit = PEAR * STEAM;
motor = sqrt(42.0);
printf("%g\n", cos(motor));
...
> Solution B:
> using #define
> pros: decouple, error codes could be defined in different .h file
> cons: macro is bad. no type safe
"Macro is bad" is -- well, that statement is bad.
> Solution C:
>
> typedef struct Error
> {
> int value;
>
> } Error;
>
> static Error const ERROR_OUT_OF_SPACE = { 123 };
Aside: Are you aware that ERROR_OUT_OF_SPACE is a reserved
name whenever <errno.h> is included?
> pros: type safe. decouple, the error code type is no longer bound with
> all the error definition
> cons: I don't know any one doing it this way so I'm not sure if it has
> some drawbacks or is it bad for (runtime/space) performance.
> If using pure C, user could not compare the error value directly but
> have to compare the inner "value" member which is not convenience.
This is type safe, but in a limited way. If the struct
declaration is visible (as it would have to be to allow access
to the embedded member), there's nothing to prevent someone
creating and returning an Error value you've never heard of:
Error do_something(void) {
...
Error x;
x.value = rho * sin(theta);
return x;
}
.... could inject an "unauthorized" code into your system. This
could be either a pro or a con, depending on your point of view.
You could regain some type safety by declaring the struct as
an incomplete type in the public header, declaring some instances
of it, and using pointers to those instances as the error codes:
// In the header:
typedef struct Error *Error;
extern struct Error OUT_OF_SPACE;
extern struct Error OUT_OF_SIGHT;
extern struct Error OUT_OF_MIND;
...
// Usage:
Error do_something(void) {
...
return &OUT_OF_MIND;
}
Since `struct Error' is incomplete, no one can create a new
instance to point at and thereby give rise to unknown codes.
Of course, even a moderately determined antagonist can still
cause trouble with a cast:
Error be_troublesome(void) {
return (Error)42;
}
To the wider question, I don't think there's a "One size fits
all" solution. Sometimes you need only a success/failure report,
and this sort of machinery is overkill. Sometimes success and
failure come in several kinds (non-overlapping, not too many), and
schemes of the sort you describe may be appropriate. Sometimes you
need to report several things about a failure or success (not just
"login failed" but "login failed because authentication server did
not respond because no connection to authentication server because
a network cable is unplugged"), and a single-code-per-failure-mode
approach would suffer combinatorial explosion.
Instead of seeking a single "best" error-reporting scheme (which
I doubt exists), I suggest you contemplate the library or other
facility that you're building, and ask yourself what kinds and amounts
of status information the callers can make good use of. You're likely
to come up with different scenarios for different facilities, so pick
a facility-specific approach -- and implement it well.
--
Eric Sosman
d