Exceptions in C with Longjmp and Setjmp(di.unipi.it)
di.unipi.it
Exceptions in C with Longjmp and Setjmp
http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
12 comments
Thirdly: Any malloc'd pointers that are only tracked on the stack end up being lost when you unwind the stack.
This third one makes using longjmp for exceptions almost untenable in real life code. You can make it easier to track and free memory correctly by using a pool allocator for the whole program, which may or may not be realistic, but I once wrote a webserver this way (from scratch, hey it was the 90s).
This third one makes using longjmp for exceptions almost untenable in real life code. You can make it easier to track and free memory correctly by using a pool allocator for the whole program, which may or may not be realistic, but I once wrote a webserver this way (from scratch, hey it was the 90s).
Wrap malloc in a function that push malloc'ed pointer on a global stack, insert NULL at the point where a try happens. When you throw an exception, simply free everything on the stack until the previous NULL. Problem solved.
Better yet, simply free memory at the exception throw site, if possible.
Better yet, simply free memory at the exception throw site, if possible.
> Wrap malloc in a function that push malloc'ed pointer on a global stack, insert NULL at the point where a try happens. When you throw an exception, simply free everything on the stack until the previous NULL. Problem solved.
This frees everything, which isn't what you want. What if you've passed your pointer off to somewhere else where it's still in use?
> Better yet, simply free memory at the exception throw site, if possible.
Most of the time, your exceptions are coming from low levels, like IO libraries, that don't know anything about the memory that you've allocated.
This frees everything, which isn't what you want. What if you've passed your pointer off to somewhere else where it's still in use?
> Better yet, simply free memory at the exception throw site, if possible.
Most of the time, your exceptions are coming from low levels, like IO libraries, that don't know anything about the memory that you've allocated.
> Most of the time, your exceptions are coming from low levels, like IO libraries, that don't know anything about the memory that you've allocated.
Yeah, but that's why you put exception handlers around anything that you expect to throw an exception, IO being a big one. Dealing with memory cleanup is definitely something that should be handled in the function allocating the memory.
Yeah, but that's why you put exception handlers around anything that you expect to throw an exception, IO being a big one. Dealing with memory cleanup is definitely something that should be handled in the function allocating the memory.
So every function now needs a try/catch? Doesn't this kind of defeat the purpose?
Every call to a function that can throw an exception should have a try/catch block, so that things can be properly cleaned up. Global exception handlers are seldom useful outside of reporting the existence of an error for future usage.
Kind of defeat the point of exceptions in the first place. It becomes more like an "enhance error return value" thing.
The thing about an exception is that you can't do about it in a number of scopes. When you encounter the exception you just just jump to the scope where you can do something about it.
The thing about an exception is that you can't do about it in a number of scopes. When you encounter the exception you just just jump to the scope where you can do something about it.
In that case, wouldn't it be simpler to explicitly check for error return on every call to such a function, and eliminate the need for an exception mechanism?
But every function can potentially throw an exception...
That makes more sense. I assumed he meant to free memory before throwing the exception.
Good point, but then again, control over memory is kind of the C killer feature, so you should know how memory flows around in your program.
If you use libraries that use internal malloc'ed memory, etc, you have to clean at the exception call site by calling the library cleanup function.
If you use libraries that use internal malloc'ed memory, etc, you have to clean at the exception call site by calling the library cleanup function.
> This third one makes using longjmp for exceptions almost untenable in real life code.
No, it's pretty simple. Postgres does it with something called regions: your code essentially becomes transaction-based, where resources that need cleanup are put into a region associated with a try block, and all remaining resources at the end of the block's (successful or failed) execution are freed. It requires slightly more work than in C++, obviously, because you're not using C++.
No, it's pretty simple. Postgres does it with something called regions: your code essentially becomes transaction-based, where resources that need cleanup are put into a region associated with a try block, and all remaining resources at the end of the block's (successful or failed) execution are freed. It requires slightly more work than in C++, obviously, because you're not using C++.
This is nice. It's pretty much how the pool allocator "integration" works. I say integration in quotes because with the pool allocator you (also) have to be careful at the try site that any pools or subpools used in the code that could throw will be freed.
If you like this article, the book C Interfaces and Implementations[1], goes into this exact technique in great detail, along with many other useful ones.
I picked it up after tptacek recommended it here on HN, and it's definitely worth every penny if you want to do any C programming on a reasonably sized project.
[1] http://www.amazon.com/gp/product/0201498413/
I picked it up after tptacek recommended it here on HN, and it's definitely worth every penny if you want to do any C programming on a reasonably sized project.
[1] http://www.amazon.com/gp/product/0201498413/
here is a great post from Owen Taylor in 1999 after this idea was repeatedly raised for the "g" stack
on Linux:
http://mail.gnome.org/archives/gnome-list/1999-December/msg0...
it kept coming up, too: http://mail.gnome.org/archives/gtk-devel-list/2001-February/...
Here is what GLib 2.0 ended up with instead, which works well: http://developer.gnome.org/glib/2.31/glib-Error-Reporting.ht...
http://mail.gnome.org/archives/gnome-list/1999-December/msg0...
it kept coming up, too: http://mail.gnome.org/archives/gtk-devel-list/2001-February/...
Here is what GLib 2.0 ended up with instead, which works well: http://developer.gnome.org/glib/2.31/glib-Error-Reporting.ht...
It's worth mentioning here that exceptions are notoriously unsafe even in C++ programs, where automated ctors and dtors should mitigate these concerns (but inevitably don't because of pointers).
If you have some C++ code linked, and use RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initial...) then you won't have the destructors of the objects in the stack getting called. For example if you had a profiling kind of marker, that relies on C++ (instead of begin() end() kind of thing).
Similarly, jumping forward over C++ objects declared on the stack (also when using goto) will skip their construction.
ReactOS actually makes heavy use of exception handling in C. The wiki page is a bit messy, but here it is: http://www.reactos.org/wiki/PSEH
A practical example of this is libpng:
http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
I wouldn't go so far as to call that "practical." It's a cute trick, but it becomes really annoying when trying to write libpng bindings for any environment other than plain C.
PostgreSQL also uses longjmp to implement TRY/CATCH macros:
https://github.com/postgres/postgres/blob/master/src/include...
https://github.com/postgres/postgres/blob/master/src/include...
as someone who has used libpng I'd describe it as an example of why this is a terrible idea.
It can even go much further than this. I have a version that
- supports arbitrarily nested try/catch statement (even in the catch or try parts)
- catching multiple exception in each clause
- supports exception hierarchy (exception can "inherit" other exceptions)
I'll probably publish it one day, but it needs a little cleaning pass. If you are interested, let me know.
I'll probably publish it one day, but it needs a little cleaning pass. If you are interested, let me know.
If you're interested in similar implementations, Microsoft has a non-standard try-except statement in their C library:
http://msdn.microsoft.com/en-us/library/s58ftw19(v=vs.80).as...
That is not a "similar" implementation, and it's not in the "library". It exposes the OS/ABI level exception functionality (which C++ exceptions are also built atop of) to the compiler.
Wasn't exception handling based on longjmp and setjmp abandoned in gcc in favor of dwarf2 especially because the former implementation performs poorly? Does the same approach yield different results for C instead of C++?
I think the requirement of the ETRY macro solidifies this as a neat trick, and not something you actually want to use in a project.
Forget ETRY once and you can kiss your afternoon goodbye while you debug.
Forget ETRY once and you can kiss your afternoon goodbye while you debug.
Ever left a semicolon off of a struct definition? Forgetting end statements/tokens will frequently lead to debugging pain, but that doesn't make it any less valid. As noted elsewhere, ReactOS uses a very similar structure to great effect.
Missing a semicolon has a chance of giving a useful error message. Without knowing what ETRY translates to, you have no hope of finding here it's missing.
On one hand, I agree. On the other, POSIX already has EINTR... (and yes, this does cause lots of bugs.)
The current implementation can't be nested...
First, you rarely want to use setjmp() and longjmp(), but _setjmp() and _longjmp(), or sigsetjmp(..., 0) and siglongjmp(..., 0) to avoid saving and restoring the signal mask. You'll rarely be modifying the signal mask in between, and the additional kernel calls to save/restore the signal mask are pretty expensive.
Second, any function that uses setjmp() or friends should have its variables declared volatile (or at least those that you rely on after executing a longjmp()). Otherwise, you risk that they do not have the proper value after a longjmp(). In particular, variables that are stored in registers and were modified between setjmp() and longjmp() will typically be restored to the value they had when setjmp() has called, not the value they had when you called the function that resulted in a longjmp() (and optimizing compilers may introduce further bugs). Unfortunately, declaring a variable as volatile will typically prevent quite a few optimizations, but it's still necessary for correctness.