Throwing Exceptions with POSIX/ISO C
After showing
pass-by-reference in C, I've been thinking about how to do a clean implementation of exceptions in C.
So
here is my POSIX/ISO C implementation of C++/Java/Python-style exceptions, written with glib flavour for taste. I couldn't get rid of the final g_try_end, and don't think can be got rid of. Leave comment if you can!
I plan to integrate this with glib after adding the thread-private stuff (-:.
/* header */
#include <glib.h>
#include <setjmp.h>
void g_exception_set_error (GError *err);
GError *g_exception_get_error (void);
void g_exception_set_jmp_buf (jmp_buf *jmpbuf);
jmp_buf *g_exception_get_jmp_buf (void);
G_GNUC_NORETURN void g_exception_throw (GError *err, int depth);
#define g_try_begin \
{ \
int __g_except_depth; \
jmp_buf __g_except_jmp_buf; \
GError *__g_except_error = NULL; \
jmp_buf *__g_except_jmp_buf_save = g_exception_get_jmp_buf (); \
g_exception_set_jmp_buf (&__g_except_jmp_buf); \
if (!(__g_except_depth = setjmp(__g_except_jmp_buf)) || \
(g_exception_set_jmp_buf (__g_except_jmp_buf_save), \
__g_except_error = g_exception_get_error (), \
FALSE)) \
{
#define g_catch(Domain, Code, err) \
} \
else if ((!(Domain+0) || (Domain+0) == __g_except_error->domain) && \
(!( Code+0) || ( Code+0) == __g_except_error->code )) \
{ \
GError *err = __g_except_error;
#define g_try_end \
} \
else \
g_raise; \
g_exception_set_error (NULL); \
g_exception_set_jmp_buf (__g_except_jmp_buf_save); \
}
#define g_throw(err) \
G_STMT_START { \
GError *__g_except_throw_error = (err); \
if (__g_except_throw_error) \
g_exception_throw (__g_except_throw_error, 1); \
} G_STMT_END
#define g_raise \
G_STMT_START { \
if (__g_except_error) \
g_exception_throw (__g_except_error, __g_except_depth + 1); \
} G_STMT_END
/* implementation */
static GError *_g_except_error_current;
static jmp_buf *_g_except_jmp_buf_current;
void
g_exception_set_error (GError *err)
{
if (_g_except_error_current)
g_free (_g_except_error_current);
_g_except_error_current = err;
}
GError *
g_exception_get_error (void)
{
return _g_except_error_current;
}
void
g_exception_set_jmp_buf (jmp_buf *jmpbuf)
{
_g_except_jmp_buf_current = jmpbuf;
}
jmp_buf *
g_exception_get_jmp_buf (void)
{
return _g_except_jmp_buf_current;
}
void
g_exception_throw (GError *err, int depth)
{
jmp_buf *target = g_exception_get_jmp_buf ();
if (!target)
g_error ("Uncaught exception (depth %d): %s", depth, err ? err->message : "(null)");
g_exception_set_error (err);
longjmp (*target, depth);
}
/* test case */
static void
my_read_file (const char *filename)
{
GError *err = NULL;
GIOChannel *stream;
stream = g_io_channel_new_file (filename, "r", &err);
g_throw (err);
/* ... */
g_io_channel_shutdown (stream, TRUE, &err);
g_throw (err);
}
int
main (int argc, char **argv)
{
g_try_begin {
if (argc > 1)
my_read_file (argv[1]);
} g_catch (G_FILE_ERROR, G_FILE_ERROR_ACCES, e) {
g_warning ("Oh oh: %s; ignoring", e->message);
} g_catch (,, e) {
g_message ("The message is: %s; chaining up", e->message);
g_raise;
} g_try_end
return 0;
}
(the feed is probably broken again, check my blog page for syntax-highlighted source.)