Error handling style in C
pixelstech.net
pixelstech.net
The examples did not have any resources to clean up, and that is what makes error handling in C painful. In the absence of any cleanup routines, this will do:
return (
do_something() == SUCCESS &&
do_something_else() == SUCCESS &&
do_final_thing() == SUCCESS) ? SUCCESS : FAILURE;
Of course, once you add resources to clean up or error codes that are meaningful (not just success/fail) error handling gets more painful.You should not try to perfect something as mundane as error handling. Just write the damn code and get over it.
int init_abc()
{
if (!init_a())
goto err_a;
if (!init_b())
goto err_b;
if (!init_c())
goto err_c;
return 1;
err_c:
cleanup_b();
err_b:
cleanup_a();
err_a:
return 0;
}
seems to be the cleanest way to do what it does in C. For what it's worth, it is the way a lot of error handling is done in the Linux kernel. char *foo = 0, *bar = 0;
if((foo = malloc(X)) == NULL || (bar = malloc(Y)) == NULL)
goto cleanup;
make_me_millions(foo, bar);
cleanup:
free(bar);
free(foo);
In this case, and many cases like it, there's no need to have two jump targets, because one is good enough. You'll have to declare the variables early on anyway to avoid warnings/errors from definitions that cross jump labels.So there's probably nothing wrong with multiple jump targets but that might not be needed with well-behaving cleanup functions.
You really need to check the specification on each function. free is defined that free(NULL) is no-op, but there are other things where that is not the case. Also, that code is not portable since NULL does not have to be 0.
I was always taught that (void *)0 is a valid definition of NULL, but that 0 was not necessarily.
Sure, but I need to do that anyway and constantly, since I use far too many API's to memorize. It's also convenient to make cleanup(NULL) a no-op for the API's you create.
NULL is zero. Even if the standard would allow it to be something else, it's not relevant with the compilers and platforms I use. There's no point in being a language lawyer just to achieve "portability" to some imaginary platform where a byte is 7 bits and NULL == 0xdeadbeef. Make a list of compilers and platforms you support and forget the rest if you want to get stuff done.
int init_abc()
{
if(!init_a())
return 0;
if(!init_b())
cleanup_a();
return 0;
if(!init_c())
cleanup_a();
cleanup_b();
return 0;
return 1;
}[1] https://github.com/torvalds/linux/blob/master/mm/shmem.c#L99...
Unstructured goto's that do not form simple loops or skips are going to make the position in code a much more complicated notion. And the position in code is crucial to understanding the meaning of variables, for example.
I find that those who think that any given programming concept is "inherently" bad to be dangerous, especially if they were just taught that way.
http://c.learncodethehardway.org/book/learn-c-the-hard-waych...
It's using the gotos, but I make macros that hide the actual word "goto" away so that idiots (who think goto is bad because of a ranting "paper" written decades past) will not notice it.
That and I print out error diagnostics, the location of the error at the time, and the errno. These macros probably saved me a good decade of time finding errors in C.
Line number and file macros are freaking awesome. Out of everything on that page those should be the major takeaway for the reader.
I also think it would be a good idea to add some context to why the goto is so useful in those examples. If any of the checks fail, you're in a full-stop, stick-a-fork-in-us-we're-done error state. Since you know that the function's operations have failed, and that you should be cleaning up everything immediately, goto is the right choice.
C++11 and the latest C have standard equivalents for these, but I'm too lazy to look them up right now..
jmp_buf errbuf;
int result;
if( !( result = set_jmp ) )
{
/* Code to do on fail. Result may be some error code */
}
else
{
someaction( par1, par2, ..., errbuf);
anotheraction( par1, par2, ..., errbuf);
/* Etc. */
}
errbuf might be global if you prefer, but I'd rather avoid them. When something is wrong called function calls longjmp(errbuf, errorcode).1. It can be called from any place in code.
2. Its call can be written in function. You don't write so much ifs whenever you call it.
if (!init_stuff(bar)) {
return FALSE;
}
if (!do_the_thing(bar)) {
return FALSE;
}
return TRUE;that is how you handle errors in C.
https://www.securecoding.cert.org/confluence/display/seccode...
If there is nothing to clean up, error checks and early exit. But only if it is one nested level deep. Any more and someone reading your code may miss it.
When there is a stack of things to be cleaned up use multiple goto targets at the end and fall through them when everything is ok.
Try to keep the nesting to a minimum. I used to indent my C with 2 spaces. Now that I use mainly use Python I have switched to 4 spaces in C and it helps keep things readable and reminds me not to get too deeply nested.
Mix and match as needed. But I think the most important thing is to keep the nesting to a minimum, that makes it much easier for those who come after you to maintain things. If you must have deep nesting or complicated logic at least make sure you do a good job of commenting what it is supposed to be doing.
Historical context is VERY important: At the time - 1968, goto was often used as the main form of control. "break" and "continue" were not universally accepted as a good idea - in fact, the idea of block scopes for if/then/else wasn't universally accepted!
e.g. in the Fortran of the day, the IF statement was followed by up to 3 numbers: line numbers to go to if the IF argument was positive, zero or negative. Wanted to break out of a loop? locate a suitable number and jump to it. That was also the case for BASIC, APL, and other languages of the day. Note: line numbers, not descriptive labels (which were a luxury added to most of these languages some 20 years later). That was a big part of the problem.
The result was a lot of what became known as "spaghetti code" - even code that had no conditionals would jump back and forth to the point that identifying an unconditional thread was similar to pulling one spaghetto out of a lump of spaghetti.
Goto is not inherently bad - but at the time, its use was (in the wild) more often than not problematic. With better language semantics -- chiefly block scoping and break/continue style loop exits -- there became much fewer reasons to use goto, and it became unfashionable to use it at all.
But it still has its place:
a) breaking out of multiple scopes when the language does not natively support it. (perl, java do; c, c++ don't)
b) one way switching to a scope that doesn't naturally nest with other scopes, such as in the case of error handling. Exceptions can sometime provide a good way to do that without goto, but not always (and not every language has them)
c) efficiently threading code. The python interpreter got a boost of ~20% a few years back IIRC by the introduction of a (computed) goto alternative to the plain C "switch" decoder. One could argue that it's an optimization the compiler should do, but apparently no compiler did at the time (And I am not sure one does today).
"goto" is not inherently evil; like C macros, it solves problems that the underlying language did not get to solve in the "most proper" way. You could eliminate them for having evil uses, but you better address all the use cases, or the language expressiveness will suffer. As far as I know, no language -- including LISP -- has made it always simpler to NOT use goto.
do
{
if (!do_something( bar )) {
break;
}
if (!init_stuff( bar )) {
break;
}
if (!prepare_stuff( bar )) {
break;
}
return do_the_thing( bar );
}while(0);
return 0; int foo(int bar) {
if (!do_something(bar))
goto do_something_error;
if (!init_stuff(bar))
goto init_stuff_error;
if (!prepare_stuff(bar))
goto prepare_stuff_error;
return do_the_thing(bar);
prepare_stuff_error:
unprepare_stuff(bar);
init_stuff_error:
deinit_stuff(bar);
do_something_error:
undo_something(bar);
return 0;
}Although I believe your jumps are in the wrong place logically speaking: if do_something() errored out, logically speaking you should not have to undo_something because do_something cleaned up its crap. So the jumps should be after the cleanups (and you should never need to unprepare_stuff).
Just as the opening of a file is not in a protected scope (if opening the file fails, you don't close it)
“…you should never need to unprepare_stuff”
I was just poking fun at the useless names.
http://vilimpoc.org/research/raii-in-c/
Despite the evil that can be made of GOTO, it has it's uses.