Exception management in C

Some languages offer features to allow exception management, or exception handling. Here by exception I mean an exceptional event which should not happen in the normal flow of an algorithm. C does not provide such a feature. Wouldn't it be nice to have such a feature in C too ?

Why use exception management ?

The only mention of 'exceptional' and 'normal' in the sentence above should trigger a red alarm in every one's head. What's exceptional ? What's normal ? It surely depends on the context of every project. If you're writing a software for a satellite in outer space, or a software running in a cosy environment like your bedroom, a bit flip due to radiation surely doesn't look the same to you. And there you have again some egg's war about what's an exception and how you should care about. So disclaimer, I don't mean to say with this article what should be considered an exception, neither that you have to do exception management, neither that if you do you have to do it the way I introduce here. It's really just about introducing a method which you could use if you think it fits your needs.

The way I've been taught to handle exceptions in C was to return an error code from the function susceptible to encounter an exception (like printf returning a negative value if an output error occured). If the function already has to return a value, it may use the same 'channel', like again printf whose returned value is the number of characters printed if no error occured. If the returned value's type does not allow to use the same channel, a global variable can be used instead (like errno when fopen fails), or a reference to a variable can be passed in argument and this variable is set as needed.

So, if there is already a way to manage exception, why bother looking for another one ? I think these methods are unsastifying because they are inconsistent while representing the same concept, and they put the exception at the place of something conceptually different: the input or the output of the function. In other word they pollute the interface of the function. A more elegant way would be to have a dedicated third channel for the exception. Moreover, a key component of an exception management system is the interruption of the flow of the algorithm implemented by the function. This component is left unaddressed by the methods above.

These considerations define the specification of the exception management solution I was hoping for in C: not cluttering the input/output interface of the function, not cluttering the code of the 'normal' algorithm of the function, and allowing interruption of the normal flow of the algorithm when an exception occurs.

How to implement an exception management in standard C.

The base idea to implement exception management in C is to use the setjmp/longjmp instructions. The idea is not new, Peter Van Der Linden speaks about it in his book "Expert C Programming" in 1994, and others, EmptyQ for example, did it before. The setjmp/longjmp instructions allow to 'bookmark' a point in time in the execution of a program and come back to it later. Yes, "Back to the Future" in C, sounds great no ? At first it looks like a goto, but it's a bit different. A goto can only move the execution pointer to somewhere in the same scope. longjmp can instead jump anywhere that has been passed through since the beginning of the execution. The code below gives a simple example of how to use setjmp and longjmp.

Even on a small example like this, it's already become spaghetti code, so I hope you agree it should be avoided or used with great care. Embedded in the exception management framework introduced here, it's constrained to a safe usage, with the only trap being not declaring the necessary variables as volatile, but this is warned by the compiler so you're safe.

Then, how does it fit the requirements ? To identify the exception we can use the value passed by longjmp, and leave the input/output of the functions clean. The interruption of the flow of the algorithm is fulfilled by longjmp jumping back to setjmp. And the processing of the exception is done in separate blocks of code, those called by setjmp according to the value sent by longjmp. That's a perfect match ! It gives something like this:

Now we can make it look prettier thanks to our good friend the precompiler:

(in a previous version of this article I was using raise() instead of raiseExc(), this was conflicting the raise() function defined in signal.h, hence the correction)

And with a little more macros we can easily add a default block to manage all exceptions without their own management block, and share blocks between several exceptions:

Finally, to allow try/catch blocks inside other try/catch blocks, we need several env variables. This can be done with an array of jmp_buf and a variable to keep track of the current depth in the try/catch blocks:

And here we are. With a few more security and conveniences (conversion from exception ID to string; exception forwarding; ...) which I won't detailed here (because I've already did here) you have a complete, clean, safe and easy to use exception management framework in plain standard C.

Example of use.

To conclude I'll give an example of how to use it and how it can make the code cleaner. I'll consider here the loading of a CSV file. I think it's a good example because it uses memory allocation and input/output operation, which, in case of failure, could probably be considered as exceptional relatively to the loading algorithm itself (again, it really depends on the context). I'll use LibCapy for the implementation of the try/catch framework, and make some shortcuts to simplify the example.

More about the volatile qualifier

Lets consider the following trivial example. We have a function which open a file, do something on it which may raise an exception, then we use try/catch to ensure the file is closed wether or not an exception occured:

The file pointer is not declared volatile and can be modified inside and outside the try/catch blocks. Then the compiler raises correctly the warning variable ‘fp’ might be clobbered by ‘longjmp’ or ‘vfork’. According to what I wrote in this article the solution would be to use the volatile qualifier, as follow:

Unfortunately this doesn't work in this case because the qualifier doesn't match the interface of fclose, then the compiler raises the warning passing argument 1 of ‘fclose’ discards ‘volatile’ qualifier from pointer target type. The solution I found is to use a structure instead of the qualifier to store all the necessary volatile variables:

This compiles without warning (whatever the optimization level), and works as expected (confirmed by checking with valgrind). My understanding (not 100% sure) is as follow. volatile will avoid the optimization to move the value of the variable to some register where it will get clobbered cause it is part of what's restored during the longjmp. By putting the variable into a structure, which isn't optimised to a register cause it's not a basic type, it prevents it the same way to be itself put into a register for optimization, without the trouble of qualifying its type. If you're more savvy and can confirm/disprove me I'd like to hear about you !

2021-09-23
in All, C programming,
178 views
Copyright 2021-2024 Baillehache Pascal