7 ms·
What I don't get is, why are we still adding more undefined behaviour to the standard? For example, C++23 got std::unreachable. From my dips into Rust, I expec
by wolletd 2y ago
What I don't get is, why are we still adding more undefined behaviour to the standard?
For example, C++23 got std::unreachable. From my dips into Rust, I expected something similar to std::abort, terminating the program in some sane and possibly helpful way.
However, in C++, std::unreachable just invokes undefined behaviour when called. It's not usable as a guard from programming errors, it's just an optimization hint. You still have to write the guard yourself.
I'm left wondering about the use-cases for this.
- bun_terminator 2y agoIt's faster and/or easier to implement. Sometimes a lot
- H8crilA 2y agoAnd if you ever reach it then maybe the program will crash, or maybe demons will start flying out of your nose. No one really knows, and that's what makes undefined behavior exciting!
- flohofwoe 2y agostd::unreachable is actually a quite useful optimisation tool. For instance if you are sure that a switch-case default branch is actually unreachable you can put a std::unreachable into the default-branch to hint the compiler that it may remove a range check in front of the switch-case jump table access (since you promised to the compiler that the default branch is never reached). It's a double edged sword of course. If control flow actually hits the default branch, then all bets are off (because that means the code will access an out-of-range jump table slot and jump somewhere into the wilderness). AFAIK compilers are free to perform something like Rust's panic when std::unreachable is actually hit, but that makes only sense in debug mode. Because in the above example, the compiler would need to add a range check to figure out if panic needs to be called and that would completely defeat the idea of removing that range check in the first place.
- planede 2y agoI recommend omitting the default case and putting std::unreachable() outside the switch and omitting the default label for the sole reason that compilers are more likely to warn for a missed case label this way (-Wswitch vs -Wswitch-enum in gcc/clang, the former is included in -Wall, the latter isn't included even in -Wextra). This also allows expressing intent: no default label means that I meant to handle all cases, and having a default means that I opted into a fallback, please don't warn. That's probably why -Wswitch-enum isn't enabled by default, too many false positives without a convenient way to suppress the warning.
- flohofwoe 2y agoHmm, how would that look like? Like this? if ((val < min) || (val >= max)) { __builtin_unreachable(); } switch (val) { ... } I haven't actually tried that, but if it works as intended it would actually be better yeah (not really in the case where I'm using it, because I'm switching on an integer, not an enum).
- planede 2y agoMore like: switch (e) { case A: foo(); break; case B: bar(); break; } std::unreachable(); instead of: switch (e) { case A: foo(); break; case B: bar(); break; default: std::unreachable(); } The former is more likely to produce a warning if there is an enumeration C that you forgot to handle, or you added an enumeration C and missed a switch-case to update. edit: duh, it's supposed to be returns here instead of breaks.
- flohofwoe 2y agoBut then all case branches hit the std::unreachable. How can that work in practice?
- gravescale 2y ago
- Xeamek 2y agoIt doesn't 'invoke' undefined behavior in the sense that it calls some function 'doRandomShit()'. What happens is: Compilers sees unreachable and assumes that this codepath will never run, so it just yeet it out of existence. For example, maybe it will remove the preceding if-statement and rather then check its condition, just insert true or false. Afterall, why waste computing on checking condition if we know it will never resolve to the unreachable path. In Rust, the same if-statement won't be optimized away, and if the code happens to go for the unreachable path, it will call panic. Now, the example is a simplification, but it's just to demonstrate that UB comes from compilers (verry aggressively) assuming UB will never happen, and not because someone decided to "add it".
- tialaramex 2y agoRust has core::hint::unreachable_unchecked, which is an unsafe function that, since we promised never to call it, also promises never to return. This has Undefined Behaviour if you call it, since you formally promised not to (the consequence of unsafely promising something and then going back on it is usually Undefined Behaviour in Rust). Rust does also have the safe macro core::unreachable which will panic if reached, but while the compiler can assume this probably won't happen and optimise accordingly, it usually can't know that it won't happen (and if it can your decision to add the macro may be unwise) One reason C++ gets more and more Undefined Behaviour is consistency. The committee will, unless prompted hard not to, draw analogies to existing UB in the language and use that to justify making your new thing Undefined in the tricky cases. The results are pretty embarrassing.
- elsjaako 2y agoYou might be interested in the compiler option -fsanitize=undefined. I think it works for gcc and clang. I don't think it catches all undefined behavior, but it catches some.
- planede 2y agoOn libstdc++ std::unreachable() also reliably crashes if you define either _GLIBCXX_DEBUG or _GLIBCXX_ASSERTIONS. libc++ should have a similar macro. I expect MS STL to also reliably crash on debug builds here, as it's quite heavy on debug assertions in the standard library anyway by default (and debug and release builds are explicitly not ABI compatible there).
- gpderetta 2y ago-funreachable-traps
- shultays 2y agoWhat I don't get is, why are we still adding more undefined behaviour to the standard? Because it allows flexibility for compilers to implement features efficiently on various platforms. You can define undefined behavior if you wish. Make your own unreachable that prints an error and aborts gracefully when reached in debug builds, and calls std::unreachable in release builds.
- chipdart 2y ago> What I don't get is, why are we still adding more undefined behaviour to the standard? Why do you believe this is some kind of problem? Can you explain in concrete terms what issue you have with undefined behavior? > However, in C++, std::unreachable just invokes undefined behaviour when called. It does, by design and as a testament to great design choices. > It's not usable as a guard from programming errors, it's just an optimization hint. You still have to write the guard yourself. I don't understand what point you tried to make. Nothing in your comment has any relation with undefined behavior. Instead, you're complaining that in your personal opinion different languages have similar-sounding features that work differently. Oddly, you should really read up on std::unreachable, because one of the reasons explicitly stated in it's references is to "trap them to prevent further execution". To explain what this means in no ambiguous terms, the standard ensures that a) std::unreachable is valid C++ and made available to the world to have the semantics to specify that a code path is unreachable, and b) allow everyone to handle that as they see fit by flipping flags in the build system. Think about it for a second. You want to call std::abort when std::unreachable is hit. Great, go with that. I don't, and instead I want the compiler to optimize away that code path and anything it would touch, but on debug builds I want it to output a warning and trigger a breakpoint. Have you noticed that all these cases introduce behavior that's entirely different, arbitrary, and implementation-defined? How do you, as a standards committee, get to cover all possible use cases? By specifying it as undefined behavior. https://en.cppreference.com/w/cpp/utility/unreachable https://en.cppreference.com/w/cpp/utility/unreachable
- ahahahahah 2y ago> entirely different, arbitrary, and implementation-defined? > By specifying it as undefined behavior. No. If you wanted implementation-defined behavior, you'd specify it as implementation defined.