The simplest answer is that it is conceivable that a machine traps integer overflow. If that were the case, then any pointer arithmetic which wasn't confined to a single storage region might cause overflow, which would cause a trap, disrupting execution of the program. C shouldn't be obliged to check for possible overflow before attempting pointer arithmetic, so the standard allows a C implementation on such a machine to just allow the trap to happen, even if chaos ensues.
Another case is an architecture where memory is segmented, so that a pointer consists of a segment address (with implicit trailing 0s) and an offset. Any given object must fit in a single segment, which means that valid pointer arithmetic can work only on the offset. Again, overflowing the offset in the course of pointer arithmetic might produce random results, and the C implementation is under no obligation to check for that.
Finally, there may well be optimizations which the compiler can produce on the assumption that all pointer arithmetic is valid. As a simple motivating case:
if (iter - 1 < object.end()) {...}
Here the test can be omitted because it must be true for any pointer iter
whose value is a valid position in (or just after) object
. The UB for invalid pointer arithmetic means that the compiler is not under any obligation to attempt to prove that iter
is valid (although it might need to ensure that it is based on a pointer into object
), so it can just drop the comparison and proceed to generate unconditional code. Some compilers may do this sort of thing, so watch out :)
Here, by the way, is the important difference between unspecified
behaviour and undefined
behaviour. Comparing two pointers (of the same type) with ==
is defined regardless of whether they are pointers into the same object. In particular, if a
and b
are two different objects of the same type, end_a
is a pointer to one-past-the-end of a
and begin_b
is a pointer to b
, then
end_a == begin_b
is unspecified; it will be 1
if and only if b
happens to be just after a
in memory, and otherwise 0
. Since you can't normally rely on knowing that (unless a
and b
are array elements of the same array), the comparison is normally meaningless; but it is not undefined behaviour and the compiler needs to arrange for either 0
or 1
to be produced (and moreover, for the same comparison to consistently have the same value, since you can rely on objects not moving around in memory.)