You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
10 KiB
248 lines
10 KiB
/*=== |
|
cexcept.h 2.0.1 (2008-Jul-19-Sat) |
|
http://www.nicemice.net/cexcept/ |
|
Adam M. Costello |
|
http://www.nicemice.net/amc/ |
|
|
|
An interface for exception-handling in ANSI C (C89 and subsequent ISO |
|
standards), developed jointly with Cosmin Truta. |
|
|
|
Copyright (c) 2000-2008 Adam M. Costello and Cosmin Truta. |
|
This software may be modified only if its author and version |
|
information is updated accurately, and may be redistributed |
|
only if accompanied by this unaltered notice. Subject to those |
|
restrictions, permission is granted to anyone to do anything |
|
with this software. The copyright holders make no guarantees |
|
regarding this software, and are not responsible for any damage |
|
resulting from its use. |
|
|
|
The cexcept interface is not compatible with and cannot interact |
|
with system exceptions (like division by zero or memory segmentation |
|
violation), compiler-generated exceptions (like C++ exceptions), or |
|
other exception-handling interfaces. |
|
|
|
When using this interface across multiple .c files, do not include |
|
this header file directly. Instead, create a wrapper header file that |
|
includes this header file and then invokes the define_exception_type |
|
macro (see below). The .c files should then include that header file. |
|
|
|
The interface consists of one type, one well-known name, and six macros. |
|
|
|
|
|
define_exception_type(type_name); |
|
|
|
This macro is used like an external declaration. It specifies |
|
the type of object that gets copied from the exception thrower to |
|
the exception catcher. The type_name can be any type that can be |
|
assigned to, that is, a non-constant arithmetic type, struct, union, |
|
or pointer. Examples: |
|
|
|
define_exception_type(int); |
|
|
|
enum exception { out_of_memory, bad_arguments, disk_full }; |
|
define_exception_type(enum exception); |
|
|
|
struct exception { int code; const char *msg; }; |
|
define_exception_type(struct exception); |
|
|
|
Because throwing an exception causes the object to be copied (not |
|
just once, but twice), programmers may wish to consider size when |
|
choosing the exception type. |
|
|
|
|
|
struct exception_context; |
|
|
|
This type may be used after the define_exception_type() macro has |
|
been invoked. A struct exception_context must be known to both |
|
the thrower and the catcher. It is expected that there be one |
|
context for each thread that uses exceptions. It would certainly |
|
be dangerous for multiple threads to access the same context. |
|
One thread can use multiple contexts, but that is likely to be |
|
confusing and not typically useful. The application can allocate |
|
this structure in any way it pleases--automatic, static, or dynamic. |
|
The application programmer should pretend not to know the structure |
|
members, which are subject to change. |
|
|
|
|
|
struct exception_context *the_exception_context; |
|
|
|
The Try/Catch and Throw statements (described below) implicitly |
|
refer to a context, using the name the_exception_context. It is |
|
the application's responsibility to make sure that this name yields |
|
the address of a mutable (non-constant) struct exception_context |
|
wherever those statements are used. Subject to that constraint, the |
|
application may declare a variable of this name anywhere it likes |
|
(inside a function, in a parameter list, or externally), and may |
|
use whatever storage class specifiers (static, extern, etc) or type |
|
qualifiers (const, volatile, etc) it likes. Examples: |
|
|
|
static struct exception_context |
|
* const the_exception_context = &foo; |
|
|
|
{ struct exception_context *the_exception_context = bar; ... } |
|
|
|
int blah(struct exception_context *the_exception_context, ...); |
|
|
|
extern struct exception_context the_exception_context[1]; |
|
|
|
The last example illustrates a trick that avoids creating a pointer |
|
object separate from the structure object. |
|
|
|
The name could even be a macro, for example: |
|
|
|
struct exception_context ec_array[numthreads]; |
|
#define the_exception_context (ec_array + thread_id) |
|
|
|
Be aware that the_exception_context is used several times by the |
|
Try/Catch/Throw macros, so it shouldn't be expensive or have side |
|
effects. The expansion must be a drop-in replacement for an |
|
identifier, so it's safest to put parentheses around it. |
|
|
|
|
|
void init_exception_context(struct exception_context *ec); |
|
|
|
For context structures allocated statically (by an external |
|
definition or using the "static" keyword), the implicit |
|
initialization to all zeros is sufficient, but contexts allocated |
|
by other means must be initialized using this macro before they |
|
are used by a Try/Catch statement. It does no harm to initialize |
|
a context more than once (by using this macro on a statically |
|
allocated context, or using this macro twice on the same context), |
|
but a context must not be re-initialized after it has been used by a |
|
Try/Catch statement. |
|
|
|
|
|
Try statement |
|
Catch (expression) statement |
|
|
|
The Try/Catch/Throw macros are capitalized in order to avoid |
|
confusion with the C++ keywords, which have subtly different |
|
semantics. |
|
|
|
A Try/Catch statement has a syntax similar to an if/else statement, |
|
except that the parenthesized expression goes after the second |
|
keyword rather than the first. As with if/else, there are two |
|
clauses, each of which may be a simple statement ending with a |
|
semicolon or a brace-enclosed compound statement. But whereas |
|
the else clause is optional, the Catch clause is required. The |
|
expression must be a modifiable lvalue (something capable of being |
|
assigned to) of the same type (disregarding type qualifiers) that |
|
was passed to define_exception_type(). |
|
|
|
If a Throw that uses the same exception context as the Try/Catch is |
|
executed within the Try clause (typically within a function called |
|
by the Try clause), and the exception is not caught by a nested |
|
Try/Catch statement, then a copy of the exception will be assigned |
|
to the expression, and control will jump to the Catch clause. If no |
|
such Throw is executed, then the assignment is not performed, and |
|
the Catch clause is not executed. |
|
|
|
The expression is not evaluated unless and until the exception is |
|
caught, which is significant if it has side effects, for example: |
|
|
|
Try foo(); |
|
Catch (p[++i].e) { ... } |
|
|
|
IMPORTANT: Jumping into or out of a Try clause (for example via |
|
return, break, continue, goto, longjmp) is forbidden--the compiler |
|
will not complain, but bad things will happen at run-time. Jumping |
|
into or out of a Catch clause is okay, and so is jumping around |
|
inside a Try clause. In many cases where one is tempted to return |
|
from a Try clause, it will suffice to use Throw, and then return |
|
from the Catch clause. Another option is to set a flag variable and |
|
use goto to jump to the end of the Try clause, then check the flag |
|
after the Try/Catch statement. |
|
|
|
IMPORTANT: The values of any non-volatile automatic variables |
|
changed within the Try clause are undefined after an exception is |
|
caught. Therefore, variables modified inside the Try block whose |
|
values are needed later outside the Try block must either use static |
|
storage or be declared with the "volatile" type qualifier. |
|
|
|
|
|
Throw expression; |
|
|
|
A Throw statement is very much like a return statement, except that |
|
the expression is required. Whereas return jumps back to the place |
|
where the current function was called, Throw jumps back to the Catch |
|
clause of the innermost enclosing Try clause. The expression must |
|
be compatible with the type passed to define_exception_type(). The |
|
exception must be caught, otherwise the program may crash. |
|
|
|
Slight limitation: If the expression is a comma-expression, it must |
|
be enclosed in parentheses. |
|
|
|
|
|
Try statement |
|
Catch_anonymous statement |
|
|
|
When the value of the exception is not needed, a Try/Catch statement |
|
can use Catch_anonymous instead of Catch (expression). |
|
|
|
|
|
Everything below this point is for the benefit of the compiler. The |
|
application programmer should pretend not to know any of it, because it |
|
is subject to change. |
|
|
|
===*/ |
|
|
|
|
|
#ifndef CEXCEPT_H |
|
#define CEXCEPT_H |
|
|
|
|
|
#include <setjmp.h> |
|
|
|
#define define_exception_type(etype) \ |
|
struct exception_context { \ |
|
jmp_buf *penv; \ |
|
int caught; \ |
|
volatile struct { etype etmp; } v; \ |
|
} |
|
|
|
/* etmp must be volatile because the application might use automatic */ |
|
/* storage for the_exception_context, and etmp is modified between */ |
|
/* the calls to setjmp() and longjmp(). A wrapper struct is used to */ |
|
/* avoid warnings about a duplicate volatile qualifier in case etype */ |
|
/* already includes it. */ |
|
|
|
#define init_exception_context(ec) ((void)((ec)->penv = 0)) |
|
|
|
#define Try \ |
|
{ \ |
|
jmp_buf *exception__prev, exception__env; \ |
|
exception__prev = the_exception_context->penv; \ |
|
the_exception_context->penv = &exception__env; \ |
|
if (setjmp(exception__env) == 0) { \ |
|
do |
|
|
|
#define exception__catch(action) \ |
|
while (the_exception_context->caught = 0, \ |
|
the_exception_context->caught); \ |
|
} \ |
|
else { \ |
|
the_exception_context->caught = 1; \ |
|
} \ |
|
the_exception_context->penv = exception__prev; \ |
|
} \ |
|
if (!the_exception_context->caught || action) { } \ |
|
else |
|
|
|
#define Catch(e) exception__catch(((e) = the_exception_context->v.etmp, 0)) |
|
#define Catch_anonymous exception__catch(0) |
|
|
|
/* Try ends with do, and Catch begins with while(0) and ends with */ |
|
/* else, to ensure that Try/Catch syntax is similar to if/else */ |
|
/* syntax. */ |
|
/* */ |
|
/* The 0 in while(0) is expressed as x=0,x in order to appease */ |
|
/* compilers that warn about constant expressions inside while(). */ |
|
/* Most compilers should still recognize that the condition is always */ |
|
/* false and avoid generating code for it. */ |
|
|
|
#define Throw \ |
|
for (;; longjmp(*the_exception_context->penv, 1)) \ |
|
the_exception_context->v.etmp = |
|
|
|
|
|
#endif /* CEXCEPT_H */
|
|
|