[ACCEPTED]-Is there a better way to do C style error handling?-error-handling
It's a bigger problem when you have to repeat 12 the same finalizing code before each return
from 11 an error. In such cases it is widely accepted 10 to use goto
:
int func ()
{
if (a() < 0) {
goto failure_a;
}
if (b() < 0) {
goto failure_b;
}
if (c() < 0) {
goto failure_c;
}
return SUCCESS;
failure_c:
undo_b();
failure_b:
undo_a();
failure_a:
return FAILURE;
}
You can even create your own macros 9 around this to save you some typing, something 8 like this (I haven't tested this though):
#define CALL(funcname, ...) \
if (funcname(__VA_ARGS__) < 0) { \
goto failure_ ## funcname; \
}
Overall, it 7 is a much cleaner and less redundant approach 6 than the trivial handling:
int func ()
{
if (a() < 0) {
return FAILURE;
}
if (b() < 0) {
undo_a();
return FAILURE;
}
if (c() < 0) {
undo_b();
undo_a();
return FAILURE;
}
return SUCCESS;
}
As an additional 5 hint, I often use chaining to reduce the 4 number of if
's in my code:
if (a() < 0 || b() < 0 || c() < 0) {
return FAILURE;
}
Since ||
is a short-circuit 3 operator, the above would substitute three 2 separate if
's. Consider using chaining in 1 a return
statement as well:
return (a() < 0 || b() < 0 || c() < 0) ? FAILURE : SUCCESS;
One technique for cleanup is to use an while 8 loop that will never actually iterate. It 7 gives you goto without using goto.
#define NOT_ERROR(x) if ((x) < 0) break;
#define NOT_NULL(x) if ((x) == NULL) break;
// Initialise things that may need to be cleaned up here.
char* somePtr = NULL;
do
{
NOT_NULL(somePtr = malloc(1024));
NOT_ERROR(something(somePtr));
NOT_ERROR(somethingElse(somePtr));
// etc
// if you get here everything's ok.
return somePtr;
}
while (0);
// Something went wrong so clean-up.
free(somePtr);
return NULL;
You lose 6 a level of indentation though.
Edit: I'd 5 like to add that I've nothing against goto, it's 4 just that for the use-case of the questioner 3 he doesn't really need it. There are cases 2 where using goto beats the pants off any 1 other method, but this isn't one of them.
You're probably not going to like to hear 11 this, but the C way to do exceptions is 10 via the goto
statement. This is one of the reasons it is in the 9 language.
The other reason is that goto
is the 8 natural expression of the implementation 7 of a state machine. What common programming 6 task is best represented by a state machine? A 5 lexical analyzer. Look at the output from 4 lex
sometime. Gotos.
So it sounds to me like 3 now is the time for you to get chummy with 2 that parriah of language syntax elements, the 1 goto
.
Besides goto
, standard C has another construct 12 to handle exceptional flow control setjmp/longjmp
. It 11 has the advantage that you can break out 10 of multiply nested control statements more 9 easily than with break
as was proposed by someone, and 8 in addition to what goto
provides has a status 7 indication that can encode the reason for 6 what went wrong.
Another issue is just the 5 syntax of your construct. It is not a good 4 idea to use a control statement that can 3 inadvertibly be added to. In your case
if (bla) NOT_ERROR(X);
else printf("wow!\n");
would 2 go fundamentally wrong. I'd use something 1 like
#define NOT_ERROR(X) \
if ((X) >= 0) { (void)0; } \
else return -1
instead.
THis must be thought on at least two levels: how 10 your functions interact, and what you do 9 when it breaks.
Most large C frameworks I 8 see always return a status and "return" values 7 by reference (this is the case of the WinAPI 6 and of many C Mac OS APIs). You want to 5 return a bool?
StatusCode FooBar(int a, int b, int c, bool* output);
You want to return a pointer?
StatusCode FooBar(int a, int b, int c, char** output);
Well, you 4 get the idea.
On the calling function's side, the 3 pattern I see the most often is to use a 2 goto statement that points to a cleanup 1 label:
if (statusCode < 0) goto error;
/* snip */
return everythingWentWell;
error:
cleanupResources();
return somethingWentWrong;
What about this?
int NS_Expression(void)
{
int ok = 1;
ok = ok && NS_Term();
ok = ok && Emit("MOVE D0, D1\n");
ok = ok && NS_AddSub();
return ok
}
0
The short answer is: let your functions 36 return an error code that cannot possibly 35 be a valid value - and always check the 34 return value. For functions returning pointers, this 33 is NULL
. For functions returning a non-negative 32 int
, it's a negative value, commonly -1
, and 31 so on...
If every possible return value is 30 also a valid value, use call-by-reference:
int my_atoi(const char *str, int *val)
{
// convert str to int
// store the result in *val
// return 0 on success, -1 (or any other value except 0) otherwise
}
Checking 29 the return value of every function might 28 seem tedious, but that's the way errors 27 are handled in C. Consider the function 26 nc_dial(). All it does is checking its arguments 25 for validity and making a network connection 24 by calling getaddrinfo(), socket(), setsockopt(), bind()/listen() or 23 connect(), finally freeing unused resources 22 and updating metadata. This could be done 21 in approximately 15 lines. However, the 20 function has nearly 100 lines due to error 19 checking. But that's the way it is in C. Once 18 you get used to it, you can easily mask 17 the error checking in your head.
Furthermore, there's 16 nothing wrong with multiple if (Action() == 0) return -1;
. To the contrary: it 15 is usually a sign of a cautious programmer. It's 14 good to be cautious.
And as a final comment: don't 13 use macros for anything but defining values if 12 you can't justify their use while someone 11 is pointing with a gun at your head. More 10 specifically, never use control flow statements 9 in macros: it confuses the shit out of the 8 poor guy who has to maintain your code 5 7 years after you left the company. There's 6 nothing wrong with if (foo) return -1;
. It's simple, clean 5 and obvious to the point that you can't 4 do any better.
Once you drop your tendency 3 to hide control flow in macros, there's 2 really no reason to feel like you're missing 1 something.
A goto statement is the easiest and potentially 7 cleanest way to implement exception style 6 processing. Using a macro makes it easier 5 to read if you include the comparison logic 4 inside the macro args. If you organize the 3 routines to perform normal (i.e. non-error) work 2 and only use the goto on exceptions, it 1 is fairly clean for reading. For example:
/* Exception macro */
#define TRY_EXIT(Cmd) { if (!(Cmd)) {goto EXIT;} }
/* My memory allocator */
char * MyAlloc(int bytes)
{
char * pMem = NULL;
/* Must have a size */
TRY_EXIT( bytes > 0 );
/* Allocation must succeed */
pMem = (char *)malloc(bytes);
TRY_EXIT( pMem != NULL );
/* Initialize memory */
TRY_EXIT( initializeMem(pMem, bytes) != -1 );
/* Success */
return (pMem);
EXIT:
/* Exception: Cleanup and fail */
if (pMem != NULL)
free(pMem);
return (NULL);
}
It never occurred to me to use goto
or do { } while(0)
for 13 error handling in this way - its pretty 12 neat, however after thinking about it I 11 realised that in many cases I can do the 10 same thing by splitting the function out 9 into two:
int Foo(void)
{
// Initialise things that may need to be cleaned up here.
char* somePtr = malloc(1024);
if (somePtr = NULL)
{
return NULL;
}
if (FooInner(somePtr) < 0)
{
// Something went wrong so clean-up.
free(somePtr);
return NULL;
}
return somePtr;
}
int FooInner(char* somePtr)
{
if (something(somePtr) < 0) return -1;
if (somethingElse(somePtr) < 0) return -1;
// etc
// if you get here everything's ok.
return 0;
}
This does now mean that you get 8 an extra function, but my preference is 7 for many short functions anyway.
After Philips 6 advice I've also decided to avoid using 5 control flow macros as well - its clear 4 enough what is going on as long as you put 3 them on one line.
At the very least Its reassuring 2 to know that I'm not just missing something 1 - everyone else has this problem too! :-)
Use setjmp.
http://en.wikipedia.org/wiki/Setjmp.h
http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.html
http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
#include <setjmp.h>
#include <stdio.h>
jmp_buf x;
void f()
{
longjmp(x,5); // throw 5;
}
int main()
{
// output of this program is 5.
int i = 0;
if ( (i = setjmp(x)) == 0 )// try{
{
f();
} // } --> end of try{
else // catch(i){
{
switch( i )
{
case 1:
case 2:
default: fprintf( stdout, "error code = %d\n", i); break;
}
} // } --> end of catch(i){
return 0;
}
#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)
int
main(int argc, char** argv)
{
TRY
{
printf("In Try Statement\n");
THROW;
printf("I do not appear\n");
}
CATCH
{
printf("Got Exception!\n");
}
ETRY;
return 0;
}
0
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.