快捷搜索:

Exceptions in C with Longjmp and Setjmp

Exceptions in C with Longjmp and Setjmp
Abstract

This document describes a very simple implementation (with many limitations) of a system to add exceptions on top of C using the libc calls longjump and setjump. This system does not pretend to be really useful in practice but it is a useful lesson about longjump and setjump with a fun example.


Introduction

Exception are a very powerful way to program error safe programs. Exceptions let you write straight code without testing for errors at each statement. In modern programming languages, such as C++, Java or C#, exceptions are expressed with the try-throw-catch statement.

... try { ... /* error prone statements */ ... } catch(SomeExceptionType e) { ... /* do something intelligent here*/ ... } ...

In previous example every exception raised by operations performed in try-block is passed to the right catch-black. If the exception type match SomeExceptionType than the code in that block is executed. Otherwise the exception is passed to the try-block that contains the actual one (if any).

Our solution is not a fully functional try-throw-catch system. It does not forward exceptions from one block to one more external if no handler is provided.

Real exception mechanisms need run-time support. We only want to explore the potentiality of longjmp and setjmp function with a non trivial example.

Longjmp And SetJmp

ANSI-C provide a lot of functions: math functions (log, sqrt...), string handling functions (strdup, strcmp, ...) and I/O functions (getc, printf, ...). All these functions are widely used and simple to understand (...strtok is not so intuitive after all...): only two functions are considered strange beasts.

These functions are longjmp and setjmp.

longjmp and setjmp are defined in setjmp.h header file...

#include <setjmp.h>

...and are defined as follows:

int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);

setjmp takes a jmp_buf type variable as only input and has a strange return behavior: it returns 0 when invoked directly and when longjmp is invoked with the same jmp_buf variable it returns the value passed as second argument of longjmp.

Do you think that this is obscure? Strange? Without sense?

Probably you are right! The behavior of there functions is really strange: you have a function (setjmp with two return values).

setjmp and longjmp mechanism works as follows: when setjmp is invoked the first time it returns 0 and fill the jmp_buf structure with the calling environment and the signal mask. The calling environment represents the state of registers and the point in the code where the function was called. When longjmp is called the state saved in the jmp_buf variable is copied back in the processor and computation starts over from the return point of setjmp function but the returned value is the one passed as second argument to longjmp function.

Probably now you are thinking something like: "Hey dude are you kiddin' me?". The replay is "No". The behavior is exactly the one stated before.

There are 10 kind of people in the world:

people thinking that this is awful (and probably are asking themselves why only two cases if there are 10 kind of people)

people thinking that it can be amazing!

The rest of document is for second ones.

Basic Try-Catch

First version is a real simple one. Probably if you are here you know the solution. (this solution was presented also by other authors... my contribution is represented by the second and the third version of the solution).

The general idea is to map TRY statement on if statement. The first time that it is invoked it return 0 and the executed code is the one stated in then branch. CATCH statement is simply the else statement. When the THROW statement is executed it simply calls the longjmp function with the second parameter equals to 1 (or anything not 0).

#include <stdio.h> #include <setjmp.h> #define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){ #define CATCH } else { #define ETRY } }while(0) #define THROW longjmp(ex_buf__, 1) int main(int argc, char** argv) { TRY { printf("In Try Statement\n"); THROW; printf("I do not appear\n"); } CATCH { printf("Got Exception!\n"); } ETRY; return 0; }

In our solution we provide also an ETRY statement that represents the end of try-throw-catch block. This is needed because we include all operations performed by try-throw-catch block in a do...while(0) block. This solution has two main reasons:

all the TRY-ETRY expression is treated as a single statement

we can define multiple, not nested, TRY-ETRY statements in the same block (reuse of ex_buf__ variable).

The following represents the compilation and execution steps of the previous example.

[nids@vultus ttc]% gcc ex1.c [nids@vultus ttc]% ./a.out In Try Statement Got Exception! [nids@vultus ttc]% Adding Exceptions

Real exception systems have the possibility to define various kinds of exceptions. These kinds are mapped over types and catch statements intercept exceptions using these types.

您可能还会对下面的文章感兴趣: