If you're not a compiler writer, you probably have a mental model of undefined behavior that works like this:
if (expr->invokes_undefined_behavior()) {
// Yay! Silly user!
shit_on_users_code();
}
Sure, you probably don't think it's so brazenly stated, but the general sentiment from compiler users tends to be that all you have to do is delete that if statement and everyone's happy.But that's not how compilers work, not in the slightest. Instead, the undefined behavior effects tend to work like a chain: you dereferenced a pointer, therefore it's now safe to assume on any subsequent path that it will always be safe to dereference that pointer, therefore we can move that later load out of that loop even though we don't know if the loop will be executed in the first place. And the reason that logic works is because we're allowed to optimize under the assumption of undefined behavior in one of those steps.
Now in some cases--signed integer overflow and strict aliasing come most readily to mind--there is a single knob you can twist to disable the undefined behavior (and most compilers let you twist that knob with command-line flags). Arguing whether or not the standard itself should dictate a different position on that knob is one thing, and it's by no means a bad discussion to have. But instead arguing "let's let you keep undefined behavior, but let's try to specify what you can do with undefined behavior" runs into the problem that it's very difficult to actually specify what somewhat-constrained undefined behavior really means. Look up the massive email threads on LLVM that try to tease what poison and undef actually mean.