1 | /*===
|
---|
2 | cexcept.h 2.0.1 (2008-Jul-19-Sat)
|
---|
3 | http://www.nicemice.net/cexcept/
|
---|
4 | Adam M. Costello
|
---|
5 | http://www.nicemice.net/amc/
|
---|
6 |
|
---|
7 | An interface for exception-handling in ANSI C (C89 and subsequent ISO
|
---|
8 | standards), developed jointly with Cosmin Truta.
|
---|
9 |
|
---|
10 | Copyright (c) 2000-2008 Adam M. Costello and Cosmin Truta.
|
---|
11 | This software may be modified only if its author and version
|
---|
12 | information is updated accurately, and may be redistributed
|
---|
13 | only if accompanied by this unaltered notice. Subject to those
|
---|
14 | restrictions, permission is granted to anyone to do anything
|
---|
15 | with this software. The copyright holders make no guarantees
|
---|
16 | regarding this software, and are not responsible for any damage
|
---|
17 | resulting from its use.
|
---|
18 |
|
---|
19 | The cexcept interface is not compatible with and cannot interact
|
---|
20 | with system exceptions (like division by zero or memory segmentation
|
---|
21 | violation), compiler-generated exceptions (like C++ exceptions), or
|
---|
22 | other exception-handling interfaces.
|
---|
23 |
|
---|
24 | When using this interface across multiple .c files, do not include
|
---|
25 | this header file directly. Instead, create a wrapper header file that
|
---|
26 | includes this header file and then invokes the define_exception_type
|
---|
27 | macro (see below). The .c files should then include that header file.
|
---|
28 |
|
---|
29 | The interface consists of one type, one well-known name, and six macros.
|
---|
30 |
|
---|
31 |
|
---|
32 | define_exception_type(type_name);
|
---|
33 |
|
---|
34 | This macro is used like an external declaration. It specifies
|
---|
35 | the type of object that gets copied from the exception thrower to
|
---|
36 | the exception catcher. The type_name can be any type that can be
|
---|
37 | assigned to, that is, a non-constant arithmetic type, struct, union,
|
---|
38 | or pointer. Examples:
|
---|
39 |
|
---|
40 | define_exception_type(int);
|
---|
41 |
|
---|
42 | enum exception { out_of_memory, bad_arguments, disk_full };
|
---|
43 | define_exception_type(enum exception);
|
---|
44 |
|
---|
45 | struct exception { int code; const char *msg; };
|
---|
46 | define_exception_type(struct exception);
|
---|
47 |
|
---|
48 | Because throwing an exception causes the object to be copied (not
|
---|
49 | just once, but twice), programmers may wish to consider size when
|
---|
50 | choosing the exception type.
|
---|
51 |
|
---|
52 |
|
---|
53 | struct exception_context;
|
---|
54 |
|
---|
55 | This type may be used after the define_exception_type() macro has
|
---|
56 | been invoked. A struct exception_context must be known to both
|
---|
57 | the thrower and the catcher. It is expected that there be one
|
---|
58 | context for each thread that uses exceptions. It would certainly
|
---|
59 | be dangerous for multiple threads to access the same context.
|
---|
60 | One thread can use multiple contexts, but that is likely to be
|
---|
61 | confusing and not typically useful. The application can allocate
|
---|
62 | this structure in any way it pleases--automatic, static, or dynamic.
|
---|
63 | The application programmer should pretend not to know the structure
|
---|
64 | members, which are subject to change.
|
---|
65 |
|
---|
66 |
|
---|
67 | struct exception_context *the_exception_context;
|
---|
68 |
|
---|
69 | The Try/Catch and Throw statements (described below) implicitly
|
---|
70 | refer to a context, using the name the_exception_context. It is
|
---|
71 | the application's responsibility to make sure that this name yields
|
---|
72 | the address of a mutable (non-constant) struct exception_context
|
---|
73 | wherever those statements are used. Subject to that constraint, the
|
---|
74 | application may declare a variable of this name anywhere it likes
|
---|
75 | (inside a function, in a parameter list, or externally), and may
|
---|
76 | use whatever storage class specifiers (static, extern, etc) or type
|
---|
77 | qualifiers (const, volatile, etc) it likes. Examples:
|
---|
78 |
|
---|
79 | static struct exception_context
|
---|
80 | * const the_exception_context = &foo;
|
---|
81 |
|
---|
82 | { struct exception_context *the_exception_context = bar; ... }
|
---|
83 |
|
---|
84 | int blah(struct exception_context *the_exception_context, ...);
|
---|
85 |
|
---|
86 | extern struct exception_context the_exception_context[1];
|
---|
87 |
|
---|
88 | The last example illustrates a trick that avoids creating a pointer
|
---|
89 | object separate from the structure object.
|
---|
90 |
|
---|
91 | The name could even be a macro, for example:
|
---|
92 |
|
---|
93 | struct exception_context ec_array[numthreads];
|
---|
94 | #define the_exception_context (ec_array + thread_id)
|
---|
95 |
|
---|
96 | Be aware that the_exception_context is used several times by the
|
---|
97 | Try/Catch/Throw macros, so it shouldn't be expensive or have side
|
---|
98 | effects. The expansion must be a drop-in replacement for an
|
---|
99 | identifier, so it's safest to put parentheses around it.
|
---|
100 |
|
---|
101 |
|
---|
102 | void init_exception_context(struct exception_context *ec);
|
---|
103 |
|
---|
104 | For context structures allocated statically (by an external
|
---|
105 | definition or using the "static" keyword), the implicit
|
---|
106 | initialization to all zeros is sufficient, but contexts allocated
|
---|
107 | by other means must be initialized using this macro before they
|
---|
108 | are used by a Try/Catch statement. It does no harm to initialize
|
---|
109 | a context more than once (by using this macro on a statically
|
---|
110 | allocated context, or using this macro twice on the same context),
|
---|
111 | but a context must not be re-initialized after it has been used by a
|
---|
112 | Try/Catch statement.
|
---|
113 |
|
---|
114 |
|
---|
115 | Try statement
|
---|
116 | Catch (expression) statement
|
---|
117 |
|
---|
118 | The Try/Catch/Throw macros are capitalized in order to avoid
|
---|
119 | confusion with the C++ keywords, which have subtly different
|
---|
120 | semantics.
|
---|
121 |
|
---|
122 | A Try/Catch statement has a syntax similar to an if/else statement,
|
---|
123 | except that the parenthesized expression goes after the second
|
---|
124 | keyword rather than the first. As with if/else, there are two
|
---|
125 | clauses, each of which may be a simple statement ending with a
|
---|
126 | semicolon or a brace-enclosed compound statement. But whereas
|
---|
127 | the else clause is optional, the Catch clause is required. The
|
---|
128 | expression must be a modifiable lvalue (something capable of being
|
---|
129 | assigned to) of the same type (disregarding type qualifiers) that
|
---|
130 | was passed to define_exception_type().
|
---|
131 |
|
---|
132 | If a Throw that uses the same exception context as the Try/Catch is
|
---|
133 | executed within the Try clause (typically within a function called
|
---|
134 | by the Try clause), and the exception is not caught by a nested
|
---|
135 | Try/Catch statement, then a copy of the exception will be assigned
|
---|
136 | to the expression, and control will jump to the Catch clause. If no
|
---|
137 | such Throw is executed, then the assignment is not performed, and
|
---|
138 | the Catch clause is not executed.
|
---|
139 |
|
---|
140 | The expression is not evaluated unless and until the exception is
|
---|
141 | caught, which is significant if it has side effects, for example:
|
---|
142 |
|
---|
143 | Try foo();
|
---|
144 | Catch (p[++i].e) { ... }
|
---|
145 |
|
---|
146 | IMPORTANT: Jumping into or out of a Try clause (for example via
|
---|
147 | return, break, continue, goto, longjmp) is forbidden--the compiler
|
---|
148 | will not complain, but bad things will happen at run-time. Jumping
|
---|
149 | into or out of a Catch clause is okay, and so is jumping around
|
---|
150 | inside a Try clause. In many cases where one is tempted to return
|
---|
151 | from a Try clause, it will suffice to use Throw, and then return
|
---|
152 | from the Catch clause. Another option is to set a flag variable and
|
---|
153 | use goto to jump to the end of the Try clause, then check the flag
|
---|
154 | after the Try/Catch statement.
|
---|
155 |
|
---|
156 | IMPORTANT: The values of any non-volatile automatic variables
|
---|
157 | changed within the Try clause are undefined after an exception is
|
---|
158 | caught. Therefore, variables modified inside the Try block whose
|
---|
159 | values are needed later outside the Try block must either use static
|
---|
160 | storage or be declared with the "volatile" type qualifier.
|
---|
161 |
|
---|
162 |
|
---|
163 | Throw expression;
|
---|
164 |
|
---|
165 | A Throw statement is very much like a return statement, except that
|
---|
166 | the expression is required. Whereas return jumps back to the place
|
---|
167 | where the current function was called, Throw jumps back to the Catch
|
---|
168 | clause of the innermost enclosing Try clause. The expression must
|
---|
169 | be compatible with the type passed to define_exception_type(). The
|
---|
170 | exception must be caught, otherwise the program may crash.
|
---|
171 |
|
---|
172 | Slight limitation: If the expression is a comma-expression, it must
|
---|
173 | be enclosed in parentheses.
|
---|
174 |
|
---|
175 |
|
---|
176 | Try statement
|
---|
177 | Catch_anonymous statement
|
---|
178 |
|
---|
179 | When the value of the exception is not needed, a Try/Catch statement
|
---|
180 | can use Catch_anonymous instead of Catch (expression).
|
---|
181 |
|
---|
182 |
|
---|
183 | Everything below this point is for the benefit of the compiler. The
|
---|
184 | application programmer should pretend not to know any of it, because it
|
---|
185 | is subject to change.
|
---|
186 |
|
---|
187 | ===*/
|
---|
188 |
|
---|
189 |
|
---|
190 | #ifndef CEXCEPT_H
|
---|
191 | #define CEXCEPT_H
|
---|
192 |
|
---|
193 |
|
---|
194 | #include <setjmp.h>
|
---|
195 |
|
---|
196 | #define define_exception_type(etype) \
|
---|
197 | struct exception_context { \
|
---|
198 | jmp_buf *penv; \
|
---|
199 | int caught; \
|
---|
200 | volatile struct { etype etmp; } v; \
|
---|
201 | }
|
---|
202 |
|
---|
203 | /* etmp must be volatile because the application might use automatic */
|
---|
204 | /* storage for the_exception_context, and etmp is modified between */
|
---|
205 | /* the calls to setjmp() and longjmp(). A wrapper struct is used to */
|
---|
206 | /* avoid warnings about a duplicate volatile qualifier in case etype */
|
---|
207 | /* already includes it. */
|
---|
208 |
|
---|
209 | #define init_exception_context(ec) ((void)((ec)->penv = 0))
|
---|
210 |
|
---|
211 | #define Try \
|
---|
212 | { \
|
---|
213 | jmp_buf *exception__prev, exception__env; \
|
---|
214 | exception__prev = the_exception_context->penv; \
|
---|
215 | the_exception_context->penv = &exception__env; \
|
---|
216 | if (setjmp(exception__env) == 0) { \
|
---|
217 | do
|
---|
218 |
|
---|
219 | #define exception__catch(action) \
|
---|
220 | while (the_exception_context->caught = 0, \
|
---|
221 | the_exception_context->caught); \
|
---|
222 | } \
|
---|
223 | else { \
|
---|
224 | the_exception_context->caught = 1; \
|
---|
225 | } \
|
---|
226 | the_exception_context->penv = exception__prev; \
|
---|
227 | } \
|
---|
228 | if (!the_exception_context->caught || action) { } \
|
---|
229 | else
|
---|
230 |
|
---|
231 | #define Catch(e) exception__catch(((e) = the_exception_context->v.etmp, 0))
|
---|
232 | #define Catch_anonymous exception__catch(0)
|
---|
233 |
|
---|
234 | /* Try ends with do, and Catch begins with while(0) and ends with */
|
---|
235 | /* else, to ensure that Try/Catch syntax is similar to if/else */
|
---|
236 | /* syntax. */
|
---|
237 | /* */
|
---|
238 | /* The 0 in while(0) is expressed as x=0,x in order to appease */
|
---|
239 | /* compilers that warn about constant expressions inside while(). */
|
---|
240 | /* Most compilers should still recognize that the condition is always */
|
---|
241 | /* false and avoid generating code for it. */
|
---|
242 |
|
---|
243 | #define Throw \
|
---|
244 | for (;; longjmp(*the_exception_context->penv, 1)) \
|
---|
245 | the_exception_context->v.etmp =
|
---|
246 |
|
---|
247 |
|
---|
248 | #endif /* CEXCEPT_H */
|
---|