Exceptional C
Tags: c, exceptions
Published: 2021-01-06 16:32:50



Don't worry, this is *not* an article about how great the C language is. No flames ahead. This is about how I added *exception handling* to C in 100 lines of code, comments included (available here: https://github.com/rdentato/try ).

Exceptions are a way to signal that something unexpected happened and some special measures need to be taken to be able to continue the program (or just exit the program if there is no way the program can execute).

Intended as a way to cleanly handle errors, they have been, sometimes, streteched to become a sort of glorified `GOTO` which led to some suspicion on their effectiveness (we all know that goto is evil, right?). 

The idea is simple: when an *exception* is *thrown*, the control jumps back to a specified portion of the program, where the exception is *caught*, analyzed and proper actions are taken.

Languages that offers exceptions (C++, Java, Python, ...) do it with the `try`/`catch` instructions. Something like:

    try {
        ... Your code here with calls to distant functions
    }
    catch {
        ... The code that is executed if an exception has been thrown 
        ... by the code in the try block or any function called by it.
    }

Error handling in C, instead, relies on functions returning error codes and/or setting the variable `errno`. It will be up to the caller to determine if an error occurred, taking the right measures and possibly propagating it up in the calling chain in a meaningful way.
This can be not an easy task because, to do it right, you need to properly plan how the errors will propagate in your applications and you may end up with one of this two, unfavourable, scenarios:
   - The overall logic gets more complicated just to gracefully handle errors;
   - Errors that are really exceptional (e.g. no more memory, file systems full, ...) cause the program to exit or, even, are not handled at all.

I do claim that there are situations where a C program could be written *better* (i.e. it would easier to write and to mantain) if we only had exceptions in C.

We don't have the `try`/`catch` block in C but we do have something that can serve the same purpose: `setjmp()`/`longjmp()`.
They allow a *non local* goto: you set the return point with `setjmp()` and get back to it with a `longjmp()`.
This mechanism is quite seldom used (and quite rightly so!) and involves saving the status in a variable of type `jmp_buf` that must be passed around to allow `longjmp()` to jump to the set return point in the code. Not exactly simple or clean.

I wanted to have something simple to use and that's why I created my own version of the `try`/`catch` instructions which you can find here: https://github.com/rdentato/try .

You can find on the Internet other implementations of `try`/`catch` in C (also based on `setjmp()`/`longjmp()`) but I couldn't find one that had all the features I wanted:
  - Fit nicely in the C syntax (no ugly `STARTRY`/ `ENDTRY` pairs, for example);
  - Allow throwing exceptions from any called function;
  - Nested `try` blocks,  even within called functions;
  - Information (file and line) about where the exceptions was thrown, for better error messages.
  - Thread safe. (not really a priority for me, but nice to have)

About the last point, the better I could do is to be able to have `try`/`catch` working in a single thread but not across threads, which would have been extremely confusing! 

Overall, I'm pleased with the result and wanted to share it with you all. Let me know in the comments below if you want me to write  a followup on how it works internally, I'll be happy to write one.

Here is how exception handling looks like, you can find real example in the test directory on Github.

      #include "try.h"
    
      #define OUTOFMEM    1
      #define WRONGINPUT  2
      #define INTERNALERR 3

      try_t catch = 0; // Initialize the exception stack

      void some_other_func()
      {
        ... code ...
        if (invalid(getinput())) throw(WRONGINPUT);
        ... code ...
      }

      int some_func() 
      {
        ... code ...
        try {
          ... code ...
          if (no_more_memory) throw(OUTOFMEM) 
   
          some_other_func();      // you can trhow exceptions
                                  // from other functions 
   
          ... code ...            // You MUST NEVER jump out of a try/catch block
                                  // via return, goto or break.
        }  
        catch(OUTOFMEM) {
          ... code ...
        }
        catch(WRONGINPUT) {
           ... code ...
        }
        catch() {                 // catch any other exception
           ... code ...           // otherwise the program will abort.
        }
        ... code ...
     }