gister 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 mostly true.
This is not an easy feat. We owe a debt of gratitude to the folks who cumulatively put engineer-centuries into making it so. It's Thanksgiving, and I thank you!
Errata and edit history
2022-11-29: Items 13-16 corrected and updated
The original version of this post contained the following items at positions 13-16 in the list:
- But if the line with UB isn't executed, then the program will work normally as if the UB wasn't there.
- Okay, but if the line with UB is unreachable (dead) code, then it's as if the UB wasn't there. Surprising, right? It isn't obvious why code that should be perfectly safe to delete would have any effect on the behavior of the program. But it turns out that sometimes optimizations can make some dead code live again. EDIT: This was originally footnote #6 before being moved down here.
- If the line with UB is unreachable code, then the program won't crash because of the UB.
- If the line with UB is unreachable code, then the program will at least stop running somehow and at some point.
This wording was not precise enough, and as a result the claims were arguably incorrect as stated. I have updated the post near those claims to better capture the subtleties involved.
2022-11-29: Added community-contributed items
The "False expectations around UB, in general" section now contains a selection of community-suggested items. Previously it only contained a single item (the last one in the current list) at position number 41.
Thanks to arriven, Conrad Ludgate, sharnoff, Brian Graham, and a few folks who preferred to remain unnamed, for feedback on drafts of this post. Any mistakes are mine alone.