6 ms·
A couple of caveats. First, you rarely want to use setjmp() and longjmp(), but _setjmp() and _longjmp(), or sigsetjmp(..., 0) and siglongjmp(..., 0) to avoid s
by rbehrends 15y ago
A couple of caveats.
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.
- rwmj 15y agoThirdly: 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).
- norswap 15y agoWrap 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.
- rcfox 15y ago> 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.
- daeken 15y ago> 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.
- rcfox 15y agoThat makes more sense. I assumed he meant to free memory before throwing the exception.
- sausagefeet 15y agoSo every function now needs a try/catch? Doesn't this kind of defeat the purpose?
- daeken 15y agoEvery 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.
- sausagefeet 15y agoBut every function can potentially throw an exception...
- rwallace 15y agoIn 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?
- norswap 15y agoKind 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.
- adgar 15y ago> 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++.
- rwmj 15y agoThis 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.