bonijad383
bonijad383

Reputation: 149

C++ single * and **

I am trying to understand why these two lines produce the same result (test and test2).

customClass* test= (customClass*)*(uintptr_t*)(someAddr);
customClass* test2 = *(customClass**)(someAddr);

From what I gathered it's either double dereference or pointer to pointer (in this case).

someAddr refers to pointer to pointer to customClass class in memory

Upvotes: 0

Views: 494

Answers (2)

Useless
Useless

Reputation: 67713

If you can't read complex statements, break them up into lots of simple statements.

  • (uintptr_t*)(someAddr) casts someAddr (which I assume is some form of pointer) to a pointer-to-uintptr_t.

    So, replace this with the statement

    uintptr_t* A = (uintptr_t*)(someAddr);
    
  • *(uintptr_t*)(someAddr) is now just *A which is a reference to uintptr_t

    So, replace this with

    uintptr_t& B = *A;
    
  • (customClass*)*(uintptr_t*)(someAddr) is now just (customClass*)B, which means we're converting the integer-large-enough-to-hold-a-pointer back into a pointer.

    See the second and third notes on reinterpret_cast, and replace this code with

    customClass* test = reinterpret_cast<customClass*>(B);
    

So the first line ends up as

uintptr_t* A = (uintptr_t*)(someAddr);
uintptr_t& B = *A;
customClass* test = reinterpret_cast<customClass*>(B);

Notice that since uintptr_t is being used to hold a pointer, we're really treating the original address as a pointer-to-pointer. That's how we de-reference one pointer and still end up with a pointer.

Now look again at

customClass* test2 = *(customClass**)(someAddr);

and see it's doing roughly the same thing.

The problem is that although uintptr_t and customClass* are losslessly-convertible, that doesn't mean it's OK to either alias them or to assume they're layout-compatible. You still need to know what type of object you currently have, in order to correctly convert it to the other.

Which type you currently have depends on what was stored there originally, so only one of the statements can be correct.

Upvotes: 0

Caleth
Caleth

Reputation: 62586

I am trying to understand why these two lines produce the same result (test and test2).

Because undefined behaviour means anything is allowed, even things that don't make sense1.

The value in someAddr is a pointer value, but that doesn't mean you can interpret it as whatever pointer type you like. What it points to is either a someClass *, or a uintptr_t, or something else, it can't be both at the same time.

Therefore one of those lines is breaking the strict aliasing rule, and thus has undefined behaviour. I would presume that it really is a someClass ** value, so use an appropriate cast for that, i.e. std::reinterpret_cast<someClass **>(someAddr)

  1. In this case, treating the value pointed to by someAddr as a pointer-sized number, then reinterpreting that as a someClass * matches how a someClass ** is implemented. It would be ok to have
uintptr_t otherAddr; 
memcpy(&otherAddr, reinterpret_cast<void*>(someAddr), sizeof(uintptr_t)); 
customClass* test3 = reinterpret_cast<customClass*>(otherAddr);

Upvotes: 1

Related Questions