The working draft of the standard N4659 says:
[basic.compound]
If two objects are pointer-interconvertible, then they have the same address
and then notes that
An array object and its first element are not pointer-interconvertible, even though they have the same address
What is the rationale for making an array object and its first element non-pointer-interconvertible? More generally, what is the rationale for distinguishing the notion of pointer-interconvertibility from the notion of having the same address? Isn't there a contradiction in there somewhere?
It would appear that given this sequence of statements
int a[10]; void* p1 = static_cast<void*>(&a[0]); void* p2 = static_cast<void*>(&a); int* i1 = static_cast<int*>(p1); int* i2 = static_cast<int*>(p2);
we have p1 == p2
, however, i1
is well defined and using i2
would result in UB.
To assign a pointer to the array we can use the following declaration... int a[10]; int *pa = &a[0]; Basically this assigns the memory address of a[0] the first element in array a to pointer of type int .
An array is considered to be the same thing as a pointer to the first item in the array. That rule has several consequences. An array of integers has type int*. C++ separates the issue of allocating an array from the issue of using an array.
There are apparently existing implementations that optimize based on this. Consider:
struct A { double x[4]; int n; }; void g(double* p); int f() { A a { {}, 42 }; g(&a.x[1]); return a.n; // optimized to return 42; // valid only if you can't validly obtain &a.n from &a.x[1] }
Given p = &a.x[1];
, g
might attempt to obtain access to a.n
by reinterpret_cast<A*>(reinterpret_cast<double(*)[4]>(p - 1))->n
. If the inner cast successfully yielded a pointer to a.x
, then the outer cast will yield a pointer to a
, giving the class member access defined behavior and thus outlawing the optimization.
More generally, what is the rationale for distinguishing the notion of pointer-interconvertibility from the notion of having the same address?
It is hard if not impossible to answer why certain decisions are made by the standard, but this is my take.
Logically, pointers points to objects, not addresses. Addresses are the value representations of pointers. The distinction is particularly important when reusing the space of an object containing const
members
struct S { const int i; }; S s = {42}; auto ps = &s; new (ps) S{420}; foo(ps->i); // UB, requires std::launder
That a pointer with the same value representation can be used as if it were the same pointer should be thought of as the special case instead of the other way round.
Practically, the standard tries to place as little restriction as possible on implementations. Pointer-interconvertibility is the condition that pointers may be reinterpret_cast
and yield the correct result. Seeing as how reinterpret_cast
is meant to be compiled into nothing, it also means the pointers share the same value representation. Since that places more restrictions on implementations, the condition won't be given without compelling reasons.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With