The implementation of exceptions, i.e. of jumps over multiple levels of nested functions and blocks, is much simpler in C than in C++, because there are no destructors that must be called when unwinding the stack, so it is enough to restore the CPU registers to the values correct for the program point where the exception must be caught.
This is needed because at the point where the exception is thrown it is not known whether any of the nested functions that must be skipped has modified any of the registers that a function is expected to preserve and where the original values of the registers have been saved.
Any kind of exceptions can be easily misused, which is why it is recommended to be careful with the use of "setjmp" and "longjmp", but they are not more harmful than the use of exceptions in any other language.
The only additional problem of C is that since there are no implicitly called destructors, like in C++ and similar languages, in C the programmer must do what the compiler would do in C++.
This means that if the nested functions that are skipped by a "longjmp" have allocated heap memory, opened files or sockets etc., such resources must be freed in the exception handler marked by a "setjmp".
Therefore the C programmer must keep track of the resources that might have been allocated in the nested functions, e.g. by recording the allocations in some global table.
It also doesn’t really compose well, so if library A is using it for exceptions and library B is doing something else clever then it’s hard to coordinate those two uses.
For setjmp to have a measurable performance impact, you have to be calling it an awful lot. It is just a handful of mov instructions without dependencies.
It is not. The dominant C++ exception system is based on "zero-cost exception handling", which refers to the fact that you do not call any extra code (like setjmp) until an exception is to be thrown. All of that logic is instead encapsulated in tables of exception-handling data and sophisticated unwind routines that look up those tables to figure out where to transfer control-flow to.
> For setjmp to have a measurable performance impact, you have to be calling it an awful lot.
If you built the C++ exception system on top of setjmp/longjmp, you would have to call setjmp on every instance that begins a try block. This includes implicit try blocks generated for every object that has a nontrivial destructor (which must be called if you unwind through the block). So yeah, you would be calling it an awful lot...
C++ and other languages tend to make the fast path of no exception extremely fast and store additional data about functions that allow for stack unwinding when an exception is thrown.
Correct, but reductive.
They can be used for many other things.
While the most frequent use of explicit continuations is for implementing exceptions, you are right that there are many other programming techniques based on explicit continuations (for instance coroutines).
The underlying primitive offers even more flexibility, but exceptions (in the sense of programming languages) can also be used for many other things than exceptions (in the sense of natural language, to handle exceptional cases).
Can you elaborate? What are the other ways exceptions are used when not used to handle exceptional situations?
> Yesterday I wrote that setjmp is handy and that it would be nice to have without linking the C standard library.
Related article from the same author. https://nullprogram.com/blog/2023/02/11/