Hacker News new | ask | show | jobs
by symmetricsaurus 1314 days ago
The situation in C++ is similar. With a particular compiler and with a specific set of compilation flags you can sometimes get predictable behavior even if it's undefined (and sometimes not). But, there are no guarantees and if you change compiler, flags or perhaps even some other part of the code the behavior can change.

Perhaps with out of order CPUs relying on something that is undefined could change based on adjacent instructions also?

3 comments

In case of a CPU it's safer, because the existing hardware definitely won't change, and you can hope the vendor won't do anything drastic in microcode updates.

But UB in a language with an optimizer is a bigger gamble. Even changes to code outside of your UB-tainted function may trip inlining and hot code heuristics, which may make more or less of the code visible to the optimizer, which may make it take advantage of different "undefined" assumptions.

It's not similar at all. It used to be C operations mapped 1-1 to the hardware, so undefined behavior meant unspecified but well-behaved in the sense that it would not randomly change unrelated registers. Now it means "nasal demons", i.e. the compiler can just ignore any execution path with undefined behavior and eliminate it as dead code.
For more, see https://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_20... (clearly distinguishing C* which corresponds to the hardware from "C" which is the subset of C* that excludes all undefined behaviour) and https://arxiv.org/pdf/2201.07845.pdf "How ISO C became unusable for operating systems development".
You’re gonna get the same result each time you compile. But if you change _anything_ the results can be completely different. So, I can agree that it’s not a very useful predictability.
There's one big difference between undefined behavior in C and C++. In C++, undefined behavior is ill-formed when it is constant-evaluated. And for aspects which are tricky to determine undefined behavior for, such as reinterpret_cast or type-punning in a union, they're just disallowed in constexpr code entirely. So in C++, you can't necessarily just say "well this is undefined, but it works correctly within our particular constraints", whereas you always can say that in C. That makes implementation-defined a huge difference from undefined. You can always cast signed integers to unsigned integers or vice versa in constexpr code.