Pointer Interconvertibility VS Having the Same Address

Pointer interconvertibility vs having the same address

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.

Pointer-interconvertibility of derived and base class objects and the Standard's wording on it

The point is in definition of standard-layout class.
From http://www.cplusplus.com/reference/type_traits/is_standard_layout/:

A standard-layout class is a class (defined with class, struct or union) that:

  • has no virtual functions and no virtual base classes.

  • has the same access control (private, protected, public) for all its
    non-static data members.

  • either has no non-static data members in the most derived class and
    at most one base class with non-static data members, or has no base
    classes with non-static data members.

  • its base class (if any) is itself also a standard-layout class.

  • And, has no base classes of the same type as its first non-static
    data member.

In you example class C does not match the third rule and so it is not a standard-layout class.

Also, about your question about 'or', it applies only to the second part, i.e. after 'or' the statement is still about standard-layout class object, but without non-static members.

Pointer-interconvertibility: reinterpret_cast derived to base

No, it would be undefined behaviour to attempt to use a pointer casted in such a manner.

[basic.compound] ¶4

Two objects a and b are pointer-interconvertible if:

[...] one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object [...]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.

Because Object has a non-static data member, it is not valid in standard C++ to reinterpret_cast its address to any of its base class subobjects and then use the resulting pointer. Even if Object had no non-static data members, it still would not be valid because it is not a standard-layout class.

Aliasing and pointer-interconvertability

Here

int fn(const X& x, int& p) {
int i = x.k;
p = 2;
return i + x.k;
}

X::k is int,p is a reference to int. p can be a reference to x.k.

On the other hand, here:

int fn(X<struct A>& x, X<struct B>& p) {
int i = x.k;
p.k = 2;
return i + x.k;
}

X<struct A> and X<struct B> are distinct types. There is no way to have x and p or parts of it refer to the same object.

But what if k is private and X has operator int() const returning k ?

Then nothing changes. Sloppy speaking, you need a reference/pointer to get potential aliasing. For example

struct G {};
struct H { G* g; }

void foo(G* a,H b);

Here b.g and a can point to the same G (note that this is the case no matter if b is passed by value, reference or pointer). In your example...

template<typename T>
struct X {int k; };
int fn(X<struct A>& x, X<struct B>& p)

.. the only references are x and p. They refer to objects of different types, aka different objects.

Is it safe to reinterpret_cast between a Type and a WrapperType?

Reinterpreting T (that's not a member of Wrapper<T>) as Wrapper<T> is never allowed (the F1 example).

On the other hand, I believe reinterpreting Wrapper<T> as T is allowed for standard-layout classes: (the F2 example)

[basic.compound]/4.3

Two objects a and b are pointer-interconvertible if:

— one is a standard-layout class object and the other is the first non-static data member of that object, or ...

And right below that:

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast. ...

Note that while this rule is symmetrical, it requires both objects to actually exist. If you have a reference to T pointing to the member of Wrapper<T>, then you can reinterpret it as Wrapper<T> (and the other way around). But if it points to a T object that's not a member of Wrapper<T>, then it would be UB.


Disclaimer: By "such-and-such reinterpreting is not allowed" I mean that accessing the result of the reinterpret_cast would cause UB. The cast itself shouldn't cause UB.

Is it possible to get a pointer to one subobject via a pointer to a different, unreleated subobject?

This is perfectly well-defined:

void something(int *x) {
reinterpret_cast<Point*>(x)->y = 42;
}

The Point object (p) and its x member are pointer-interconvertible, from [basic.compound]:

Two objects a and b are pointer-interconvertible if:

  • [...]
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or:
  • [...]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.

That reinterpret_cast<Point*>(x) is valid and does end up with a pointer that points to p. Hence, modifying it directly is fine. As you can see, the standard-layout part and the first non-static data member part are significant.


Although it's not like the compilers in question optimize out the extra load if you pass a pointer to p.y in and return p.x instead.



Related Topics



Leave a reply



Submit