## 32. Faking Exceptions in C

I love the breaking the structure of structured programs. We have previously implemented coroutines in C using setjmp(3) and longjmp(3). We are now going to fake exceptions in C using the same functions.

Consider a program foo: first thing on startup, it calls init() which reads a config file and opens a log file for writing.

int read_config() {
if (!(config = fopen("foo.config", "r"))) {
return 1;
}
return 0;
}

int init_log() {
if (!(log = fopen("foo.log", "a"))) {
return 1;
}
return 0;
}

int init() {
return 1;
}
if (0 != init_log()) {
fprintf(stderr, "error in init_log; rethrowing");
return 1;
}
return 0;
}

int foo() {
if (0 != init()) {
fprintf(stderr, "error in init");
return 1;
}
// awesome foo stuff
return 0;
}

Both read_config() and init_log() may fail due to file errors, and init() may fail because it calls functions that may fail. We signal failure by returning non-zero and we check for failure after each function call. This is all fairly standard stuff for C.

This style of error handling has the advantage of being very clear, but it is also quite noisy. Point in case, init() is almost three times as long as it needs to be. The overhead would be justified if we were also recovering from errors, but here, we just want to propagate them. What’s worse is that we have to do error handling after every function call, and since the code is repetitive and boring, we’re likely to forget.

We want exceptions and we get them in C with setjmp() and longjmp() which are effectively intra-function gotos.

jmp_buf fail;

if (!(config = fopen("foo.config", "r"))) {
longjmp(fail, 1);
}
}

void init_log() {
if (!(log = fopen("foo.log", "a"))) {
longjmp(fail, 1);
}
}

void init() {
init_log();
}

int foo() {
if (0 == setjmp(fail)) {
init();
// awesome foo stuff
return 0;
} else {
fprintf(stderr, "error in init");
return 1;
}
}

Like before, we use non-zero values to signal failure, but now we send them to foo() via longjmp() bypassing init() completely. On the one hand, this code is much less noisy and easier to write, but on the other, we can no longer understand the functions independently; we need to read the entire program to see its behaivour.

This scheme isn’t quite at feature parity with most other exception systems, but we can easily fix that. If we replace the single top-level jmp_buf with a linked list of jmp_bufs, we get nested exception handlers and rethrowable exceptions. If instead of sending ints via longjmp(), we send pointers to some exception struct, we get rich exceptions. If we register signal handlers, we can convert system errors like “write to broken pipe” and “division by zero” to exceptions.

As we can see, exceptions in C can be straightforward but there are some features which would be harder to implement from scratch. For instance, getting proper stack traces without having to modify each function call would be difficult. Similarly, it’s unclear how these exceptions should work with regard to threads and forking.

One amazing thing about all this is that it’s actually used in practice. Libpng, which is included directly or indirectly in most graphical applications, does error handling this way: “When libpng encounters an error, it expects to longjmp back to your routine”.

This style of error handling is also used in more modern languages. Go’s defer/panic/recover mechanism is reminiscent of setjump() and longjmp().