As a rule of thumb, yes, compilers should issue warnings where undefined behavior is obviously occurring. (And there is something to be said for compiling with -Werror). However, that's not going to always work, and there are two reasons for this.
The first reason is that undefined behavior is ultimately a statement about dynamic execution of the program. The set of expressions that could potentially cause undefined behavior is essentially all of them--every signed arithmetic expression, every pointer dereference, hell, almost all function calls in C++--and figuring out whether or not they actually do cause undefined behavior is effectively impossible at the compiler level. This is why sanitizers were developed, and also why sanitizers only work as a dynamic property.
For a concrete example, consider the following code:
extern void do_something(int * restrict a, int * restrict b, size_t n);
void my_function(int *y, int *z, size_t x, size_t n) {
if (x > n)
do_something(y, z, n);
}
This code could produce undefined behavior. Or it could not. It depends on whether or not y and z overlaps. Maybe the check of the if statement is sufficient to guarantee it. Maybe it's not. It's hard to advocate that the compiler should warn, let alone error, about this kind of code.
The second issue to be aware of is that there is a general separation of concerns between the part of the compiler that gives warnings and errors (the frontend), and the part that is actually optimizing the code. It is difficult, if not impossible, to give any kind of useful warning or error message in the guts of the optimizer; by the time code reaches that stage, it is often incredibly transformed from the original source code, to the point that its correlation with the original can be difficult to divine.
So I once came across some really weird code that broke an optimization pass I was working on. It looked roughly like this (approximate C translation of the actual IR):
if (nullptr != nullptr) {
int *x = nullptr;
do {
/* do some stuff with x */
x++;
} while (x != nullptr);
}
What hideous code creates a loop that iterates a pointer through all of memory? Why, this (after reducing the test case):
void foo() {
std::vector> x;
x.emplace_back();
}
So the crazy code was generated from the compiler very heavily inlining the entire details of the STL, and the original code was a more natural iteration from a start to an end value. The compiler figured out enough to realize that the start and end values were both null pointers, but didn't quite manage to actually fully elide the original loop in that case. Warning the user about the resulting undefined behavior in this case is completely counterproductive; it's not arising from anything they did, and there isn't much they can to do to silence that warning.