4 ms·
If a code path that's supposed to be unreachable is reached then the program is already broken. Unless it has a bug, a compiler will not make a program more bro
by fluoridation 4y ago
If a code path that's supposed to be unreachable is reached then the program is already broken. Unless it has a bug, a compiler will not make a program more broken. At worst (or best, depending on how you look at it) it will only make any bugs it already has more obvious.
- actionfromafar 4y agoYes, but what was unreachable may get reachable as I do changes to the program. I see the utility when doing highly optimized library code. But for my purposes I much prefer to stick something which flags me (exception or logging or whatever) of "this should never happen" instead of crashing. (Undefined.)
- dahfizz 4y agoIn a real codebase, this would be wrapped by a macro to abort in a debug build but gain the optimizations in a release build.
- eco 4y agocppreference suggests this as an implementation for std::unreachable but as near as I can tell neither clang nor MSVC does that (yet?).
- leni536 4y agoI would have expected the _LIBCXX_ASSERTIONS macro in libc++ to affect the behavior, similarly to many other standard library undefined behaviors.
- actionfromafar 4y agoCan't help but feel offended that my codebase isn't real. Thanks for the laugh though :-D I'd rather have solid logging and/or abort handling than some extra yak hairs shaved off the speed on my release builds. I'm weird that way.
- assbuttbuttass 4y agoIt's undefined behavior. The compiler is free to do whatever, including making your program more broken.
- fluoridation 4y agoIf you have undefined behavior, your program is already broken. No such thing as "more broken"; there's already no theoretical limit to what might happen if it gets triggered.
- carstimon 4y agoUndefined behavior is considered worse than crashing, which is typically the alternative when reaching "unreachable" codepaths. Compare these two blocks similar to the article switch (ch) { case 'a': do_a(); return; case 'd': do_d(); return; // ch is guaranteed to be 'a' or 'd' by previous code. default: assert(0); } switch (ch) { case 'a': do_a(); return; case 'd': do_d(); return; default: std::unreachable(); } If the programmer is wrong about `ch` in the first one, the program terminates. For the second one, the compiler could change it to be equivalent to if (ch == 'a') { do_a(); } else { do_d(); } return; If the programmer is wrong here, the program might `do_d()` with unintended consequences. I'd say "going down unintended codepaths" is typically considered worse than crashing. N.b. fixed last code example- thanks afiori.
- randomNumber7 4y agoI get your point, but assert is usually removed in release builds. Abort would probably be better if you want to crash the program.
- afiori 4y agoI suspect you meant to omit the `if (c == 'd')` part in the last example
- mannykannot 4y agoI take your point, but you can get the behavior of your first example, while still marking the default branch with std::unreachable(), by asserting the preconditions before the switch. This seems to me to be a pretty general equivalence. So what does std::unreachable() do here? In this particular case, and with NDEBUG defined and any level of optimization selected, I suspect that, at a minimum, the switch would be replaced as you have shown in all versions - it would take a more complex example to show how std::unreachable() makes a difference. The point is, now we have a choice - and it is one that is being offered without creating any backwards-compatibility issues. Furthermore, the original function, without assertions, is not guaranteed to crash, with or without std::unreachable(). You need some explicit checks to get a desirable response in the case where a mistake has been made, and that option is just as available whether or not you use std::unreachable(). Therefore, while I agree you have shown that not all broken variants of a given program are equivalent, this does not show that std::unreachable() is harmful.
- DougBTX 4y ago> At worst (or best, depending on how you look at it) it will only make any bugs it already has more obvious. An abort would make them obvious, surely unreachable makes them less obvious?
- fluoridation 4y agoThe point is that you're telling the compiler "I don't care what happens if control reaches here. Assume it never will and use that information to better optimize the rest". It's not an alternative to abort() or throwing because the compiler still needs to generate code for them.
- DougBTX 4y agoYes, that’s what I mean, the compiler can completely remove a branch which is unreachable, along with any checks. If it was forced to keep the checks and report an error instead, any bug would be more obvious. If the compiler removes checks for bugs, then the application could silently continue, obscuring the bug. So this is a “hide bugs but maybe improve performance” function.
- fluoridation 4y agoNo, it doesn't hide bugs. It has no defined effect on the obviousness of bugs. The reason is that modern compilers are pretty much theorem solvers, and they're able to propagate truth values in order to deduce facts about programs. For example, a compiler could deduce that since a branch is never reached, a particular pointer is never null, and could therefore skip a null check that it deduced was redundant that would have prevented a null dereference. If it turns out that the branch is reachable and the pointer is null, the pointer would be dereferenced and the program would crash immediately (typically). But UB is UB; once you hit it all behaviors are permissible, from immediate crash, to silent data corruption, to nasal demons.