Error handling via GOTO in C
Following up on my previous post, I was asked about the use of GOTO in C for error handling.
I decided to go and look at a bunch of C code bases, and it took me no time at all to find some interesting examples of this approach.
Below, you can see a small portion from a random method in the Sqlite codebase. Sqlite is considered to be one of the best C code bases out there, in terms of code quality, number and thoroughness of tests and the quality of the project as a whole. As such I think it is a good candidate for a sample.
Here you can see there there is the need to free some resources and at the same time, handle errors. This is routinely handled using a cleanup code at the end of the function that error clauses will jump to if needed.
Note that this is very different from “GOTO considered harmful”. That article talked about jumping to any arbitrary location in the program. C (and all other modern languages) limit you to jump around only inside your own method. Even though methods are quite long in the Sqlite codebase, it is very easy to follow what is going on there.
Here is another example, which is a lot more complex. I have taken it from the Linux kernel code:
Again, I had to redact a lot of code that actually do stuff to allow us to look at the error handling behavior. Now you can see something that is a lot more complex.
There are actually multiple labels here, and they jump around between one another in the case of an error. For example, if we fail to allocate the cifs_sb we jump to out_nls, which then jump to out. Failure to get the root will jump us to out_super and then fall into out. For out_free, it falls to out_nls and then jump to out.
In both the Sqlite example and the Linux example, we have two separate and related responsibility. We need to do resource cleanup, but that is complicated by the different stages that this may go through in the function. Given that, GOTO cleanup is the best option available, I think.
There are various defer style options for C, libdefer and defer macro looks better. But both have some runtime costs. I like the defer macro better, but that won’t work on MSVC, for example, as it require language extension.
Comments
I think that's actually good C code. It shows how lacking C as a language is in that it forces you to such patterns.
Tobi, As I said, these are from known good codebases, they show off good practices for C. The fact that C lacks something like
defer
is a shame.There's an interesting thread where Linus Torvalds defends the use of GOTO's
I have been advocating for this approach for years. :-) It makes the code cleaner and easier to read. And I'm not sure it's applicable to C only. There are cases in C# when there is no better options.
Long methods? That's what longjmp() is for. Duh.
"Note that this is very different from “GOTO considered harmful”. That article talked about jumping to any arbitrary location in the program. C (and all other modern languages) limit you to jump around only inside your own method. "
C doesn't have methods, it has functions.
And c does not limit you to jumping in the same function.
See: https://linux.die.net/man/3/longjmp
Bill, In this context, I'm using method and function interchangeably. There is no meaningful difference between the two that matter for this post
From the C99 standard docs: "The identifier in a
goto
statement shall name a label located somewhere in the enclosing function." I'm talking specifically about goto here. And if you are usinglongjmp
for error handling, may God have mercy upon your soul.For reference, this scares me deeply: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html Especially if you are doing that across function calls.
Very glad to see this post! There are definitely situations, especially with low-level environments, where this is the cleanest (read: most readable, least error-prone) way to handle expected errors. I have used this technique personally (many moons ago) with great success in embedded systems with no OS.
The goto is the gateway drug to the longjump!!! ;)
Just like most features/ tools, when used correctly it's not an issue. I've used GOTO for clean jumps to skip unnecessary processing or general error handling. Just don't make spaghetti logic by placing them all over the code.
For the people bashing longjmp, have a look at how exception handling is done in languages which support that. When you raise an exception, you longjmp to code up the stack to run exception filters, finally blocks, and then the catch block. C gives you the power to implement that (in a well-structured way if you are smart).
Of course, if your caller and caller are conspiring to not let your function end in a normal way, you can't use goto cleanup code (at least not with guaranteed cleanup); you need to participate by registering a finally block.
Sean, The problem is that if you are going to implement
try
/catch
/finally
in C, you might as well go to C++ anyway. And what scares me is the use oflongjmp
in an arbitrary manner entirely.The arguments here seem to be predicated upon the assertion that "Sqlite is considered to be one of the best C code bases out there", and therefore since it uses gotos they must be OK.
Gotos can be used in a safe and structured way, as evidenced by the wealth of higher level languages that implement complex and verifiable flow control with jmps at the machine code level. However, even the simpler examples you give make me go cold at the thought of having to perform a security review - confirming that there are no memory leaks, paths through the code that somehow avoid the appropriate level of clean-up, security holes where buffers are freed twice or re-used without being initialised correctly. They probably aren't, but nowadays I'm far more interested in ensuring that my code is provably correct than shaving a few microseconds of execution time for the sake of using a structured initialisation/cleanup approach (auto ptrs in C++, C#, Java).
I know that there are some applications for code that require maximum performance, but for everything else I see no need to resort to gotos.
John
Maybe the best way is to not allocate several things in same function ... This simplifies use of goto ...
A way to do a cleanup without goto is by using what is named a "dummy loop" :
// dummy loop do { int ret=some_func(); if (ret==FAILED) { break; }
} while (false);
// do some cleanup cleanup();
return FAILED; }
With that notation, you can jump to the cleanup section without using any goto. My 2 cents
Menuki, That is a GOTO, just much uglier and harder to see.
John, Sqlite specifically, in this case, has been the subject of multiple manual and automated review. The testing page is very impressive: https://www.sqlite.org/testing.html
@Oren Eini Yes, it is.
But : 1. It doesn't make purists being crazy 2. In C++, it handles object destruction well. I prefere to use a practice that works well in C and C++.
Comment preview