4 ms·
Not really. An example that explicitly crashes instead of triggering undefined behavior: If n!=0 return 1/n Is well behaved: Division by zero wont happen.
by hyperman1 3y ago
Not really. An example that explicitly crashes instead of triggering undefined behavior:
If n!=0 return 1/n
Is well behaved: Division by zero wont happen.
z=1/n
If n!=0 return z
Is not well behaved. It will crash when n=0. What the compiler does is take the existence of z as a proof that n can't be 0, and optimize the if away:
z=1/n
return z
Just like division by 0, undefined behaviour (UB) is not allowed. The difference is: UB might crash or give a nonsensical result. You can't tell beforehand. Different versions of a compiler might make different choices of crash vs nonsense. Until recently, the author calculated nonsense, and threw it away, but that was accidental. It might have crashed too.
C had a lot of UB that gets thrown away, until some minor shift in optimizations changes it to a crasher. Compare it to an insanely sharp knife: Powerfull, but the slightest error might take off your fingers, it's very hard to know if something is or is not an error, and errors mostly dont cut if your fingers, until surprise surprise they do.
Rust is much better: only unsafe code behaves like C's crazy knife. Only when you use unsafe, the rust guarantees go out of the window, because that's what the programmer explicitly asked.
- trealira 3y agoWhen you're learning to program, teachers will often tell you "The computer is dumb; it will do exactly what you say, and it can't read your intent." Implicitly, I think most people assume the same of compilers: they're dumb, and will translate an unconditional division as an unconditional division, even when it would crash. Instead, compilers seemingly try to make insights into the intent or true meaning of your code, often inaccurately, and change it using ad-hoc unintuitive rules as justification (those being the rules of undefined behavior, and what assumptions the authors are and aren't allowed to make about your code). I know they aren't actually reading your intent; it's just the result of many, many passes that each make seemingly reasonable assumptions, which together may add up to a set of unreasonable ones. > because that's what the programmer explicitly asked. No, the programmer didn't explicitly ask for their check to be deleted, even with "unsafe" written. There is no request written anywhere. At most, you can say they implied they wanted it by invoking undefined behavior. Probably, though, they just made a mistake, or assumed that behavior was defined when it wasn't (e.g. they may have thought division corresponds to a hardware division instruction, modulo optimizations like right shifting for division by 2, constant folding division by 1, etc.)
- deleted 3y ago[deleted]
- superjan 3y agoFor some background on why compilers do this, read this gist by ryg. A big issue is we write 32 bit code that runs on 64 bit architectures. https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759de5a7 https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759...
- hyperman1 3y agoThis is a very interesting read. Thanks.
- thaumasiotes 3y ago> No, the programmer didn't explicitly ask for their check to be deleted, even with "unsafe" written. There is no request written anywhere. At most, you can say they implied they wanted it by invoking undefined behavior. Probably, though, they just made a mistake, or assumed that behavior was defined when it wasn't This is a weird complaint to make here, where the compiler optimization isn't introducing a bug. If the check were left in place, the code would have exactly the same problem that it does without the check. The optimization is exactly what you hope for: something that makes the code faster without affecting its behavior in any way. Why is that bad?
- thaumasiotes 3y agoYou original summary is very poorly phrased. You do mention the problem: > By calculating y, he accidentally gives a proof to the compiler that x must be true. But the actual example is written to hide this problem. It should make clear that "y" is actually an expression that depends on x, something like "return x.then_some(x.field)". With that example, it would be immediately clear why evaluating x.field before checking x was a bug.