简体   繁体   中英

Reinterpret casting pointers to standard-layout types with common prefixes

Let's say I have the following types:

struct Common { int a, b, c; };
struct Full { int a, b, c; uint64_t x, y, z; };

Common and Full are standard-layout types, where Common is a prefix of Full . So if I put both in a union:

union U {
    Common c;
    Full f;
};

I would be allowed to read through c even if f was the active member per [class.mem]/23 .

Now the question is - is there a way for me, given a Full const* , to get a Common const* in a non-UB way?

void foo(Full const* f) {
    Common c1;
    memcpy(&c1, f, sizeof(c1)); // this obviously works, but I don't want
                                // to be copying all this stuff

    auto c2 = reinterpret_cast<Common const*>(f); // is this ok?
        // c2 and f are pointer-interconvertible iff f comes from a U
        // but why does that U actually need to exist?

    auto u = reinterpret_cast<U const*>(f); // ok per basic.lval/8.6??
    auto c3 = &u->c;                        // ok per class.mem/23??
}

Short answer: reinterpret casting an object address to a different class and accessing through the pointer is undefined behaviour.

I think the reason is to enable more easy alias analysis by the compiler. If the compiler can assume that an Object of Type X cannot alias an object of unrelated Type Y, even if they happen to be layout compatible, then it may be able to perform optimizations that it could not do otherwise.

struct X
{
  int a;
  int b;
};

struct Y
{
  int c;
  int d;
};

void updateXY(X *x, Y* y)
{
   x->a = y->c;
   x->b = y->c; // if *x and *y could alias, y->c would have to be reloaded
}

void updateXX(X *x, X* xx)
{
   x->a = xx->a;
   x->b = xx->a; // the compiler must reload xx->a because x and xx may alias
}

In fact gcc and clang do optimize updateXY more than updateXX, because it considers the aliasing.

updateXY(X*, Y*):
  mov eax, DWORD PTR [rsi] // cache y->c
  mov DWORD PTR [rdi], eax
  mov DWORD PTR [rdi+4], eax
  ret
updateXX(X*, X*):
  mov eax, DWORD PTR [rsi]
  mov DWORD PTR [rdi], eax
  mov eax, DWORD PTR [rsi] // reload y->c
  mov DWORD PTR [rdi+4], eax
  ret

It does not seem to make a difference for alias analysis whether a union between X and Y is visible to the compiler, ie the optimization is performed regardless. So in practice code that passed around pointers to non-active union members may still break. For your usecase of passing const pointers this is should be irrelevant.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM