First see this question mentioned in the comments for why it isn't well defined. The answer given concisely is that arbitrary pointer arithmetic is not possible in segmented memory models used by some (now archaic?) systems.
What is the rationale to make such behavior undefined instead of, for instance, implementation defined?
Whenever standard specifies something as undefined behaviour, it usually could be specified merely to be implementation defined instead. So, why specify anything as undefined?
Well, undefined behaviour is more lenient. In particular, being allowed to assume that there is no undefined behaviour, a compiler may perform optimisations that would break the program if the assumptions weren't correct. So, a reason to specify undefined behaviour is optimisation.
Let's consider function fun(int* arr1, int* arr2)
that takes two pointers as arguments. Those pointers could point to the same array, or not. Let's say the function iterates through one of the pointed arrays (arr1 + n
), and must compare each position to the other pointer for equality ((arr1 + n) != arr2
) in each iteration. For example to ensure that the pointed object is not overridden.
Let's say that we call the function like this: fun(array1, array2)
. The compiler knows that (array1 + n) != array2
, because otherwise behaviour is undefined. Therefore the if the function call is expanded inline, the compiler can remove the redundant check (arr1 + n) != arr2
which is always true. If pointer arithmetic across array boundaries were well (or even implementation) defined, then (array1 + n) == array2
could be true with some n
, and this optimisation would be impossible - unless the compiler can prove that (array1 + n) != array2
holds for all possible values of n
which can sometimes be more difficult to prove.
Pointer arithmetic across members of a class could be implemented even in segmented memory models. Same goes for iterating over the boundaries of a subarray. There are use cases where these could be quite useful, but these are technically UB.
An argument for UB in these cases is more possibilities for UB optimisation. You don't necessarily need to agree that this is a sufficient argument.