Because the compiler would have to add these instructions every time you use a pointer. A C++ program uses a lot of pointers. So there would be a lot of these instructions for the computer to run. The C++ philosophy is that you should not pay for features you don't need. If you want a null pointer check, you can write a null pointer check. It's quite easy to make a custom pointer class that checks if it is null.
On most operating systems, dereferencing a null pointer is guaranteed to crash the program, anyway. And overflowing an integer is guaranteed to wrap around. C++ doesn't only run on these operating systems - it also runs on other operating systems where it doesn't. So the behaviour is not defined in C++ but it is defined in the operating system.
Nonetheless, some compiler writers realized that by un-defining it again, they can make programs faster. A surprising amount of optimizations are possible because of this. Sometimes there's a command-line option which tells the compiler not to un-define the behaviour, like -fwrapv
.
A common optimization is to simply assume the program never gets to the part with the UB. Just yesterday I saw a question where someone had written an obviously not infinite loop:
int array[N];
// ...
for(int i = 0; i < N+1; i++) {
fprintf(file, "%d ", array[i]);
}
but it was infinite. Why? The person asking the question had turned on optimization. The compiler can see that there is UB on the last iteration, since array[N]
is out of bounds. So it assumed the program magically stopped before the last iteration, so it didn't need to check for the last iteration and it could delete the part i < N+1
.
Most of the time this makes sense because of macros or inline functions. Someone writes code like:
int getFoo(struct X *px) {return (px == NULL ? -1 : px->foo);}
int blah(struct X *px) {
bar(px->f1);
printf("%s", px->name);
frobnicate(&px->theFrob);
count += getFoo(px);
}
and the compiler can make the quite reasonable assumption that px
isn't null, so it deletes the px == NULL
check and treats getFoo(px)
the same as px->foo
. That's often why compiler writers choose to keep it undefined even in cases where it could be easily defined.