Friday 26 April 2013

Another attempt at try/except/finally in C

Since previous work on C cleanup functions I've been trying various means to implement try/finally in C using macros that allow a natural in-code expression of a typical form, like:

try {
  ...
  ... throw(123);
  ...
} finally or except {
  ...
}

It was not possible to use cleanup functions to implement the finally block for many reasons including difficulty of syntax construction, but ultimately because a cleanup function must return normally and so cannot chain an exception:
 It is undefined what happens if cleanup_function does not return normally. http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html 
(Or can they? as I proceed with setjmp I think they can exit with longjmp but at the cost of missing out other cleanup handlers, so maybe I just contradicted myself there.)

I was reading about call-with-current-continuation for C and was re-reminded about setjmp/longjmp and wondered if these might work. (For GENSYM see my previous post).

jmp_buf GENSY(jmp_buf);
if((errno=setjmp(GENSYM(jmp_buf)))==0) {
  ...
  ... longjmp(????, 123);
  ...

} nothing or else { // nothing for finally, and else for except
...
}

the main advantage with setjmp/longjmp is the effective go-back-to consequence of longjmp which avoids the need to recall the names of any generated reference further on in the code. The main difficulty with throw is knowing the reference ???? to use in the longjmp. It should be the most recent active jump_buf --- but how to tell which it is?

We could have a local variable in the try scope which stores the address of the most recent jmp_buf, or in other words we don't need GENSYM macro after all so long as we start a new scope:

{ jmp_buf active_jmp_buf;
if ((errno=setjmp(active_jmp_buf))==0) {
  ...
  ... throw(123);
  ...
} } nothing or else {
  ...
}

which could be generated by these

#define try { jmp_buf active_jmp_buf; \
              if ((errno=setjmp(active_jmp_buf))==0)
#define finally }
#define except } else
#define throw(thrown) longjmp(active_jmp_buf, thrown)

Note how the exception number is smuggled into the exception handler through use of errno.

After looking at some exception handler examples for C# and seeing how much like a switch statement it appeared, I decided I wanted to be able to separate exception types with their own handler and realised that a case statement might do for this:

{ jmp_buf active_jmp_buf;
  switch (setjmp(active_jmp_buf)) {
  case 0:
       {
          ...
          ... throw(123);
          ...
       }
       ...
  case 123:
       {
         ...
       } 
  }
}

Which, though incomplete (consider the lack of break statements) might be generated by something like this:

#define try { jmp_buf active_jmp_buf; \
  switch (setjmp(active_jmp_buf)) { \
  case 0:
#define finally }
#define except(exception) case exception:
#define throw(thrown) longjmp(active_jmp_buf, thrown)

and so expressed like this:

try {
  ...
  ... throw(123);
  ...
} except(123) {
  ...
} except(default) {
  ...
}

Except that we don't have enough close-braces to close the switch statement or the scope containing the jmp_buf which now contains the entire switch statement. This is the sort of problem that plagued previous attempts based on cleanup functions.

With the last new keyword being the finally or the except, any special scopes must be constrained to the try keyword that finally or except can clean them up. Therefore the switch statement is off limits as it requires an extra close-brace, although Francesco Nidito was not too proud to introduce ETRY to solve this problem with his C exceptions (along with Duff's device).

4 comments:

  1. One deficiency with try...finally/except is that any variables local to the try block are lost to the finally/except blocks.

    It might be nice to introduce an alternative syntax that preserves the try scope for the finally/except cleanup blocks

    ReplyDelete
  2. I think I may find something here to help me, especially the C lambda example which may help my earlier difficulties in pre-declaring local functions and so on.

    http://stackoverflow.com/questions/1772119/c-the-most-useful-user-made-c-macros-in-gcc-also-c99

    A lamba variable could be it's own cleanup function!

    ReplyDelete
  3. Wow, I found a good implementation here: http://p99.gforge.inria.fr/p99-html/group__try_ga9162d721fecf634c028ab873912c9cd0.html#ga9162d721fecf634c028ab873912c9cd0

    ReplyDelete
  4. And p99 shows a way to avoid curly braces for the switch statement! Use a horribly complicated nested if/else set of blocks that contains the case: directives.

    These avoid the need for curly braces! As long as it is a single statement, it will do!

    hah hah hah

    ReplyDelete