Working with and implementing C++ exceptions for 30 years now, including implementing exception handling for Windows, DOS extenders, and Posix (all very different), and then re-implementing them for D, I have sadly come to the conclusion that exceptions are a giant mistake. 1. they are very hard to understand all the way down 2. they are largely undocumented in how they're implemented 3. they are slow when thrown 4.…
> 10. has anyone yet found a legitimate use for throwing an `int`? I use that a lot in constexpr computations -- to stop the compilation, I usually do 'throw __LINE__'. -- Using a more complex type is not warranted -- there is no catching end in constexpr. -- And in case the same routine ends up called non-constexpr, it will be easy to identify the place that called 'throw' -- line numbers are unique without addition…
C++ Exceptions: Under the Hood (2013)
101–110 of 116 posts
Re: C++ Exceptions: Under the Hood (2013)
#102Earlier quoted context omitted.
> Not sure what you mean here Allocating local variables into registers rather than assigning stack locations for them. Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents. Stack maps wouldn't be necessary for non-pointers, like an integer variable. Stack maps also have their own performance problems, which is why D doesn't use them.
The term used most often for this is "spilling". I figured this is what you meant by "deregistering" but I wasn't sure, so I didn't want to assume. > Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents. I get that, which is why Java JITs don't use callee-saved registers. I mean, they use all the physical registers, of course, but their call…
Re: C++ Exceptions: Under the Hood (2013)
#103Earlier quoted context omitted.
"has anyone yet found a legitimate use for throwing an `int`?" I could image throwing an int when writing a shell utility and throwing the return value of main as an int but I suppose doing that usefully would be pretty rare. Usually one cares more about whether a shell utility is successful or not not so much about the precise reason it failed. So, while I could imagine doing that, I don't see myself going for that…
Yes, but you couldn't mix that code with code that throws an `int` for other porpoises. It becomes a global straightjacket for your code.
Re: C++ Exceptions: Under the Hood (2013)
#104> When the personality function doesn't know what to do it will invoke the default exception handler, meaning that in most cases throwing from a nothrow method will end up calling std::terminate. This is an interesting tidbit that cost me a week of debugging recently - a try/catch block at the top of the call stack wasn't catching an exception. We set up an exception handler that calls main in a try/catch block, so t…
How can that take a week to debug ? gdb would stop at the std::terminate call in your dtor, and catch throw would allow you to see exactly where the exception was thrown
Second, the std::terminate call doesn't get called from the dtor, it gets called from the stdc runtime in the call frame of the throw (with OS code in between). The stack isn't actually unwound at this point, it's more like the stdc runtime is walking up the call stack looking for a landing pad at each frame.
Third, I didn't know about how this all worked, so I was trying to piece it all together for the first time.
Yes, I saw the throw happen. But the symptom was then that the program just... terminated.
Re: C++ Exceptions: Under the Hood (2013)
#105Earlier quoted context omitted.
Rust is one such language. I wrote a bit about exception safety in Rust here: https://users.rust-lang.org/t/c-pitfalls-hard-to-avoid-that-... In short, while the problem is mitigated somewhat compared to C++, it's still one of the most common causes of bugs in unsafe Rust code. Rust programs can choose to abort on all panics, rather than unwind. Firefox does this, for example.
We do this on all our Rust code as well (1.1 million LOC at this point). While we can benefit from #[no_std] crates on crates.io, unfortunately we can't use any crates that require standard because the standard library does not propagate errors properly, so we maintain our own implementation of most of the standard library for Linux only, that propagates all errors using Result.. It's a huge pain point, but at least…
Is "panicking on allocation failure" the only example of this, or are there others?
Re: C++ Exceptions: Under the Hood (2013)
#106Earlier quoted context omitted.
What do you think of languages that use sum types for error handling, but can still unwind in a few scenarios? Reasonnable compromise, or should we get rid of all unwinding always? And if so, do we abort() or do we ask users to handle any and all possible errors.
I've read the proposals for it. It certainly looks good, yet exception handling looked good 30 years ago, too. I haven't used sum types myself, and often it takes years to discern whether things are really good ideas or not. What I personally use is the "poisoning" technique. This involves marking an object as being in an error state, much like a floating point value can be in a NaN state. Any operation on a poisoned…
Re: C++ Exceptions: Under the Hood (2013)
#107Earlier quoted context omitted.
How can that take a week to debug ? gdb would stop at the std::terminate call in your dtor, and catch throw would allow you to see exactly where the exception was thrown
Well first of all I wasn't using gdb since it's not available on the platform this code was running on. Second, the std::terminate call doesn't get called from the dtor, it gets called from the stdc runtime in the call frame of the throw (with OS code in between). The stack isn't actually unwound at this point, it's more like the stdc runtime is walking up the call stack looking for a landing pad at each frame. Third…
what I mean is that, if you run that in a debugger, you're going to see something akin to this when the crash occurs (and have your ide stop exactly where the offending exception was thrown ; I don't even have to set `catch throw` for this to work): https://ibb.co/hK3skvz - at least on windows, mac, linux. What platform are you running that does not support gdb at all ? pretty much anything that isn't a PIC16F or Z80 supports it..
Re: C++ Exceptions: Under the Hood (2013)
#108Earlier quoted context omitted.
The term used most often for this is "spilling". I figured this is what you meant by "deregistering" but I wasn't sure, so I didn't want to assume. > Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents. I get that, which is why Java JITs don't use callee-saved registers. I mean, they use all the physical registers, of course, but their call…
"spilling" usually means a variable is sometimes in a register, sometimes on the stack. "Enregistering" means it is full time in a register.
Re: C++ Exceptions: Under the Hood (2013)
#109Earlier quoted context omitted.
> they are slow when not thrown I thought the not-thrown case was pretty much zero-cost, at least in newer versions of Clang... How much of a slow down are we talking here?
The main reason is the optimizer abandons trying to figure out flow-of-control when half the expressions can throw and present a path to the catch blocks. Furthermore, this all inhibits en-registering variables, because exception unwinding doesn't restore registers. If you want your code to be fast, use 'nothrow' everywhere. I don't know about newer versions of Clang, but I recall Chandler Carruth mentioning that LLV…
I'm sorry, but this is just not true of at least the Itanium exception handling system used by Linux, macOS, etc on most architectures.
It does make the exception handling data quite large and throwing extra slow, of course.
Re: C++ Exceptions: Under the Hood (2013)
#110Earlier quoted context omitted.
I have quit using exceptions in my own code, making everything 'nothrow'. Assuming not all code you use is your own, how does this work in combination with other code (like the STL) which is not nothrow?
Generic code (like you'd find in a library) is usually done with templates. Templates in D infer `nothrow`, giving them the advantage of being implicitly `nothrow` when their arguments are also nothrow. Inferring attributes this way is a major way D works.