0

Is it possible that void* keep type-information?
I tried to make it forget the real types (B*) by cast B* b to void* v, but it seems to know its origin.
It is good-luck for me, but I don't know why.

Here is a simplified code (full demo) :-

#include <iostream>
#include <memory>
using namespace std;

class B{public: int ib=1;};
class C:public B{public: int ic=2;};

int main() {
    B* b=new C();
    void* v=b;                     //<- now you forget it!
    C* c=static_cast<C*>(v);    
    std::cout<<c->ib<<" "<<c->ic<<std::endl; //print 1 and 2 correct (it should not)
    return 0;
}

I expected c to point to the address of B* (a wrong address), but it seems to know that v is B* and correctly cast.

Question

Why it work? Does void* really remember the type?
How far can it "remember"?

Background

I am trying to create an void* abstract class here, but surprised that it works.
I want to make sure that it is an expected standard behavior.

Edit: Sorry for the newbie question.
Now, I realize that I misunderstand about void*.

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 5
    By using `static_cast`, you are *telling* it the type. If you instead told it some completely unrelated type, it might even **appear** to work. It's all just bytes underneath, being interpreted in different ways. (It would be [Undefined Behaviour](https://en.wikipedia.org/wiki/Undefined_behavior) though). – BoBTFish Apr 20 '17 at 14:11
  • The cast is only valid if `v` points to a `C`, which it happens to do. `b`, `v` and `c` happen to all point at the same address. When you use multiple inheritance that will not always be the case and the cast will produce invalid pointers. – nwp Apr 20 '17 at 14:12
  • @BoBTFish But I have already trolled it by stating that it is "C*", not "B*". – javaLover Apr 20 '17 at 14:12
  • You create a `new C`, so at the other end of the pointer is a valid byte representation of a `C` object. Why are you then surprised that by saying "at the end of this pointer is a `C`" (ie `static_cast) that you get the expected values? – BoBTFish Apr 20 '17 at 14:14

2 Answers2

7

Undefined behavior is undefined. That means you might, just might, get the "right answer" anyway.

In this particular case, it is entirely possible that your compiler's layout organizes C such that C and its base class B have the same address. Therefore, if you pretend one is a pointer to the other in an undefined way, you get correct behavior.

Today. What happens tomorrow is a matter of speculation.

This is not something you can rely on. It's just a "fortunate" happenstance.


It's a pointer to C, then a pointer to B, then a pointer to void, then a pointer to C again. All perfectly legal conversions.

No, they're not.

It's a pointer to C, then a pointer to B. Then it becomes a void*. But the standard is quite clear: if you convert a pointer to a void*, the only legal conversion is one that converts it to a pointer to the exact type that was cast from. So if you convert from B* to void*, the only legal conversion is back to B*.

Not to a type derived from B.

C++17 makes "the exact type" into "a pointer interconvertible type". But base and derived classes are not pointer-interconvertible, so it's still UB.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Got it. Yes, you would strictly need to go via `B*` on the way back up. – BoBTFish Apr 20 '17 at 14:18
  • @NicolBolas: can you add the relevant standard paragraph? – Vittorio Romeo Apr 20 '17 at 14:18
  • Can you modify my code in the way that can break it? I have randomly tried to add more fields to both classes, but it still works. – javaLover Apr 20 '17 at 14:21
  • 2
    @javaLover have `C` inherit from a non-empty class before `B`. – Quentin Apr 20 '17 at 14:23
  • 1
    @VittorioRomeo: It's a combination of things. [expr.static.cast]/13 explains the behavior of `static_cast`, and for casting to the wrong type, it simply says that the pointer value is unchanged; it *specifically* does not say that it is a pointer to an object of type `C`. The undefined behavior of accessing an object of type `B` through a pointer of type `C` is handled through [basic.lval]/8. – Nicol Bolas Apr 20 '17 at 14:23
  • @Quentin Thank! It doesn't work anymore. http://coliru.stacked-crooked.com/a/39932fa6104e4783 – javaLover Apr 20 '17 at 14:26
  • What OP did is the literal equivalent of a `reinterpret_cast`. – T.C. Apr 20 '17 at 20:22
1

(Nicol is correct; my answer is more entry-level and doesn't go into language-lawyering; still, you should find it sufficiently matches your observations in reality.)


I expected c to point to the address of B*

It does.

(a wrong address)

No, it's the right address.

but it seems to know that v is B* and correctly cast.

It knows that every C has a B base. Because that is how C is defined.

So a C* points to a region of memory with some B stuff followed by some C stuff.

Your void* has remembered nothing; all of this "intelligence" lies in the definitions of B and C.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055