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.