Home

Awesome

Exceptional

A simple Exception-handling library for C99 and up, that uses some fancy macros for true try{...}catch(err){...}finally{...} syntax!

First, include exceptional/exceptional.h in your code.

Then, use the try { ... } macro (combined with catch { ... } and/or possibly finally { ... }) to guard against errors.

Implemented using Wonderful Black Ma(cro)gic. It is written in this convoluted style to ensure that you can use the following syntax:

try {
  // some code here
  if(something_is_wrong)
    throw(error_code);
  // some code that would only be executed if not thrown
} catch(error_code) {
  // Handle error here in any way you want.
} finally {
  // Runs regardless of catching/throwing.
}

Usage Notes:

Truly exceptional, isn't it?

Post Scriptum: By looking at the Compiler Explorer it can be seen that the 'black macros' that Exceptional consists of are completely optimized away, only keeping the stack winding/unwinding and conditional jump checking in place.

Running Tests

  1. Clone or download the repository.
  2. Make sure that besides gcc you have nodejs and the node package manager (npm) installed on your system, because tests are run using the tool urchin.
  3. Run make test. This will:

Planned Future work:

  1. Rewriting the throwing/catching logic because the way a values is currently read from setjmp is not fully spec-adherent (i.e. it relies on undefined behaviour), although all common compilers allow it.
  2. I am currently thinking about adding support to throw any (user supplied) structure rather than just integers.

How does it work? Or: Demystifying the Black Magic:

For a more gradual introduction to these concepts, read 'Metaprogramming custom control structures in C' by Simon Tatham, which is really good!

  1. We want someone to be able to pass a block or single-statement after writing the macro. This means the macro should a) finish with an if(true) b) Since we want to do something after this block has returned, it should be inside of a loop. c) To make sure that the stuff afterwards is only run after the block, we need to make sure that the invariant is different when the loop is run for the second time. An example:
    for(int var = 0;var < 2; ++i)
      if(var == 1) {
        //stuff to run afterwards
      } else
    
  2. This also means that we cannot introduce any construct that ends with a } ourselves.
if(1){
  // your code
  goto label;
} else
  label:
  //Continue statements that do not end with `}` here.
  1. Unfortunately, that construct does not allow us to introduce new variables, because they would only exist inside the if's body-scope. But we can start a for-loop and use its initializer to create a new variable;
  2. This however does mean that we either need a clever way to make sure the for-loop stops, or we need to have a final 'finishing' section at the very top:
  while(1)
    if(0) {
      finished_label:
      break;
    } else
      for(int some_var;;)
        for(int some_other_var;;)
          // with at some point inside a nested for-loop:
            if(something_only_true_when_user_code_already_ran)
              goto finished_label;
  1. To ensure that the macro can be used in multiple locations, we need to make our lables unique. For this, the LINE_LABEL(name, __LINE__) macro is used.

Disclaimer

This was written to test the limits of C's macro system as well as my own understanding of low-level system languages.

While it seems to work in the limited tests that I ran on it, it should by no means be considered highly stable.

Enjoy!

Attribution

I want to thank DSMan195276 whose post on Reddit I came across, which taught me most of the macro-twiddling strategies used in this library.

He has since pointed me to this marvellous resource, which explains a lot of these techniques in more detail: https://www.chiark.greenend.org.uk/%7Esgtatham/mp/