ind of lesson one tends to learn the hard way.
- At least it won't start playing Doom if the program didn't already have the Doom source code in it. I'd be quite impressed if you made a compiler that makes programs run Doom when they encounter UB. Consider it a challenge!
Falsehoods like "but it worked fine before"
- If a UB-containing program "worked fine" previously, recompiling the program without any code changes will still produce a binary that "works fine."
- Recompiling without code changes and with the same compiler and flags will produce a binary that still "works fine."
- Recompiling as above + on the same machine will produce a binary that still "works fine."
- Recompiling as above + if you haven't rebooted the machine since the last compilation will produce a binary that still "works fine."
- Recompiling as above + with the same environment variables will produce a binary that still "works fine."
- Recompiling as above + at the same time of day and day of week as before, during a Lunar eclipse, having first sacrificed a fresh stick of RAM to the binary gods, will produce a binary that still "works fine."
Falsehoods about self-consistent behavior of UB
- Multiple runs of the program compiled as above and with the same inputs will produce the same behavior in each run.
- Those multiple runs will produce the same behavior if the program, ignoring the UB, is deterministic.
- But they will if the program is also single-threaded.
- But they will if the program also doesn't read any external data (files, network, environment variables, etc.).
Community-contributed falsehoods around UB
- Using a debugger on a UB-containing program will show program state that corresponds to the source code. This is a corrolary of falsehood #16, further explained in this post. UB can corrupt the behavior of the program both before and after the UB, so the source code you see in your editor no longer matches the actual executing program. Of course, you can still use the debugger to step through assembly instructions and view register state. But highly optimized assembly isn't easy to understand to begin with, and UB-induced weirdness will only make it harder. Overall, a situation that is best avoided. Contributed here.
- Undefined behavior is purely a runtime phenomenon.
In Rust, a counter-example is misusing
#[no_mangle]to overwrite a symbol with an incorrect type. A C++ counter-example is violations of the One Definition Rule (ODR), some of which the compiler is not required to report before causing havoc.
False expectations around UB, in general
- Any kind of reasonable or unreasonable behavior happening with any consistency or any guarantee of any sort.
The moment your program contains UB, all bets are off. Even if it's just one little UB. Even if it's never executed. Even if you don't know it's there at all. Probably even if you wrote the language spec and compiler yourself. Speaking from experience. Hopefully not one you have to relive to believe.
This is not to say that all outcomes in the list above are equally likely, or even plausible. Especially the one about running Doom. But they are all allowed, valid, spec-compliant behavior.
It's perfectly possible that your program has UB, and it's been running fine for years without issues. That's great! I'm happy to hear it! I'm not even saying you need to go back and rewrite it to remove the UB. But as you make your decisions, it's good to know the full picture of what the compiler will or won't guarantee for your program.
Honorable mention for one special assumption
"If the program compiles without errors then it doesn't have UB."
This is 100% false in C and C++.
It's also false as stated in Rust, but with one tweak it's almost true.
If your Rust program never uses
unsafe, then it should be free of UB.
In other words: causing UB without
unsafe is considered
a bug in the Rust compiler.
These are rare and you are quite unlikely to run into them.
unsafe is used, then all bets are off just as in C or C++.
But the assumption that "Safe Rust programs that compile are free of UB" is mostl