Regardless you are right there is a cost is it worth paying hard to say
Checked divs are present in release mode too. Omitting them would be UB.
Other checks (e.g. for additions) are omitted in release mode, but they were not preventing UB in the first place because they guard a wrapping addition, not an unchecked one (the kind that's UB in C).
If I wrote some code, I expect it to be present in the binary. I don't just write code to be removed by the compiler. If that expectation was wrong, compiler should inform me about that.
It's very common to write code in templates or inline functions expecting the compiler to remove it if it's not relevant on the calling site. That's part of what makes "zero cost abstractions" have zero cost at runtime.
For instance, I have a SIMD routine with extra code to process the tail (leftover elements smaller than the native vector size). When the compiler can prove that the size of the input will always be a multiple of the vector size (which is very common for my use cases), it will completely remove that tail handling code.
That's oversimplified. If after inlining and constant folding an if-condition turns out to be always true or false I would definitely expect that the compiler removes the dead branch.
This type of optimization is the base for the fabled "zero-cost-abstraction" (which isn't only a C++ thing, C code depends on it just as much), and removing those optimization would seriously tank peformance in any non-trivial codebase.
It is definitely a hopeless path.
Suppose you have the following program:
unsigned f(unsigned x) { return 2*x; }
Can this be optimized to instead perform a bit shift? That's faster than multiplying. But you wrote a multiplication operator. Should the compiler be responsible for keeping it?
The problem here is that the C compiler ever assumes that UB doesn't happen. That is empirically very much not the case, therefore the compiler should never be allowed to assume a lack of UB unless it can somehow prove that to be true. I honestly don't really care how many optimizations that would break; correctness is king. Software that goes fast is only worthwhile if it works correctly.
It's approximately all optimizations. Good news: there's already a compiler option that does exactly what you want: -O0. It's even enabled by default (unless overridden by another -O switch)!
Under the as-is rule, optimizations must not change program behavior. A C program could theoretically use out-of-bounds pointers to scan its own stack, observing whether a value occurs on the stack. Thus, the as-is rule prohibits storing local variables in registers!
But because out-of-bounds pointers are undefined behavior, the compiler can ignore the programs doing stack scanning, and so register allocation becomes possible under the as-is.
So boring old register allocation is one of those "assume that UB doesn't happen" optimizations! Of course, the compiler never actually reasons "this pointer arithmetic is out of bounds therefore I can put that variable over there into a register" -- the reasoning from undefined behavior doesn't happen at compile-time, it already happened when the "register allocation" optimization was designed. But that's the case for most "assume that UB doesn't happen" optimizations! (this is also why it's so difficult for compilers to warn about undefined behavior -- they optimize based on it without ever detecting it!)
If you want to eliminate "reasoning from undefined behavior", you'd also need to replace the "as-is" rule with something else -- an explicit list of allowed optimizations in the language standard?
Though it might be possible to have more "sensible" UB. For example, assuming daemonic non-determinism for allocation address it is possible to turn stack storage into registers, without the need for an aliasing model. Benchmarking it has the same difficulty.
My personal observation is also that it only surprises developers when they read blog posts about it and that this actually comes up quite rarely in practice.
You are otherwise correct of course.
Imagine you have a tail-recursive function and you'd like to convert it to iteration. This is only semantic preserving if there is no UB!
Or worse, if you can never assume that UB doesn't occur then you need all code to be compiled in a way that defends against data races. You can't change the allocation order on the stack, since there might be some write past the end of a buffer and now suddenly stack allocation order changes the semantics of the program. Nightmare.
Imagine this program:
int f() {
int x = 1;
a(x);
b(x);
return x;
}
Can we use constant propagation to replace "x" with 1 in the instructions? No. After all, a could smash the stack and write over x. You need a new read of that memory location every time you plan on using x.