One that was missed, which I’ve seen only in libICU. Every function takes an UErrorCode*, which must be initialised to no-error. If the function encounters an error then it will set the error state. In addition, all of the functions check the error state on entry and return immediately if it is an error. This lets you write exception-like control-flow in C, something like:
UErrorCode err = NO_ERROR;
someFn(arg, anotherArg, &err);
secondFn(arg3, &err);
lastFn(something, &err);
if (IS_ERROR(err))
{
// Handle the error now
}
You can, if the error return is a boolean. If it’s a richer type then it ends up being more verbose, something like:
ErrorCode ok = NO_ERROR;
ok = IS_ERROR(ok) ? ok : f1();
ok = IS_ERROR(ok) ? ok : f2();
ok = IS_ERROR(ok) ? ok : f3();
if (IS_ERROR(ok)) etc
You can hide that in macros, but it gets a bit more hairy. The nice thing about putting it inside the function is that chained calls don’t duplicate the error checking code and a modern branch predictor will very rapidly learn to not expect the error condition and so the extra 2-3 instructions inside each function are basically free: they’ll be predicted not-taken most of the time and just consume a small amount of i-cache (less than they would on the outside of the function at every call site).
If you’re designing a library, please please use the real bool (and enums). C99 is not new any more! It’s so annoying when all functions just return int and you need to remember when that int means a boolean where 0 == false and when it’s a code where 0 == SUCCESS.
How to get me to be quite, umm, unhappy, with you….
Pass an error code up and up and up the call graph and…. cast it to void.
I have solved more unsolvable bugs than I can count by the simple algorithm of searching for all cast return codes to void and logging the code if not success, printing a backtrace (yes you can do that in C/C++) and aborting.
Weehee! Bugs come crawling out of the wood work and you go whack whack whack and they go away for ever.
I have solved more unsolvable bugs than I can count by the simple algorithm of
searching for all cast return codes to void and logging the code if not
success, printing a backtrace (yes you can do that in C/C++) and aborting.
You are going to love this. I’ve seen code like this in at least one
library that is widely available in Linux distributions etc. I’m not
going to name and shame, because I still need to open an issue.
So we don’t actually know if our handler ever got registered.
That’s the basic outline, anyhow. It’s yet another take on the ostrich
method. I’d call the ostrich method the most widely used in C, just
because nobody I know of actually checks the return value of printf.
Error handling in C is annoying, and a lot of programmers – including
myself – are optimists.
While exit() does take an integer value, the C standard explicitly states three values you can give to it—0 (which indicates success to the operating system), EXIT_SUCCESS (which does the same thing, but nothing states it has to be 0) and EXIT_FAILURE. POSIX limits the value to 8-bits. MS-DOS (and probably Windows) has the same limit.
One that was missed, which I’ve seen only in libICU. Every function takes an
UErrorCode*
, which must be initialised to no-error. If the function encounters an error then it will set the error state. In addition, all of the functions check the error state on entry and return immediately if it is an error. This lets you write exception-like control-flow in C, something like:You saw it in libICU and libICU saw you too.
…that’s actually somewhat brilliant. Thanks for the addition!
You can turn this inside out too:
You can, if the error return is a boolean. If it’s a richer type then it ends up being more verbose, something like:
You can hide that in macros, but it gets a bit more hairy. The nice thing about putting it inside the function is that chained calls don’t duplicate the error checking code and a modern branch predictor will very rapidly learn to not expect the error condition and so the extra 2-3 instructions inside each function are basically free: they’ll be predicted not-taken most of the time and just consume a small amount of i-cache (less than they would on the outside of the function at every call site).
If you’re designing a library, please please use the real
bool
(andenum
s). C99 is not new any more! It’s so annoying when all functions just returnint
and you need to remember when thatint
means a boolean where0 == false
and when it’s a code where0 == SUCCESS
.How to get me to be quite, umm, unhappy, with you….
Pass an error code up and up and up the call graph and…. cast it to void.
I have solved more unsolvable bugs than I can count by the simple algorithm of searching for all cast return codes to void and logging the code if not success, printing a backtrace (yes you can do that in C/C++) and aborting.
Weehee! Bugs come crawling out of the wood work and you go whack whack whack and they go away for ever.
You are going to love this. I’ve seen code like this in at least one library that is widely available in Linux distributions etc. I’m not going to name and shame, because I still need to open an issue.
So we don’t actually know if our handler ever got registered.
That’s the basic outline, anyhow. It’s yet another take on the ostrich method. I’d call the ostrich method the most widely used in C, just because nobody I know of actually checks the return value of printf.
Error handling in C is annoying, and a lot of programmers – including myself – are optimists.
Sigh. I do not love. No sir, not at all, at all.
I hate code that is written to “might do something”.
It might work, it might not work, but nobody will know it didn’t unless a user is paying close attention and complains.
If the user asked the program to do something, do it or tell him why you couldn’t, don’t silently do nothing.
Sure there are “best effort” use cases, but they are few and far between and should be made clear from the function name.
If you call a function “do_x” (or “add_handler) I expect it to do x or tell me why not, not “might do x, feeling cute, might not do”.
Grrumble grrowl mutter mutter mutter.
aaaaaaaaa
at least do
exit(4206969); // lmao
or something instead of just returning!While
exit()
does take an integer value, the C standard explicitly states three values you can give to it—0 (which indicates success to the operating system),EXIT_SUCCESS
(which does the same thing, but nothing states it has to be 0) andEXIT_FAILURE
. POSIX limits the value to 8-bits. MS-DOS (and probably Windows) has the same limit.Given the context, I genuinely do not know how to respond to this. XD Thanks for the info though.
Robust material. I enjoy the reading. Congrats on the composition.
Out of all the strategies mentioned I prefer using simple enums.
I know I enter the territory of blasphemy but i also rely on the go tos to throw “exceptions” to a catch block.
I also happened to write an article on this topic, almost a decade ago. I guess I’ll add a link.