|
From: K. F. <kfr...@gm...> - 2011-02-13 22:43:41
|
Hi John-Philip!
I will take a crack at explaining this. (There's a good chance
that I'll mix things up, so corrections are solicited in advance.)
On Sun, Feb 13, 2011 at 1:57 PM, John-Philip Taylor
<Joh...@ne...> wrote:
> Good day,
>
> I'm using a void* red-black tree to store a bunch of derived classes
> and came across a peculiarity (much simplified below):
>
> The code attached yields:
>
> Integer is 12345
> Now the integer is 4254256
> Shifting the base address yields: 12345
>
> main.cpp:
>
> #include <stdio.h>
>
> class Parent{
> public:
> int i;
> };
(Note, class Parent does not have a v-table, as it is not a
"polymorphic" base class -- no virtual functions.)
> class Child : public Parent{
> public:
> virtual int MyFunction(){return 0;}
> };
(Note, class Child does have a v-table.)
> int main(){
> Parent* p;
> Child * c;
> void * v;
>
> c = new Child;
> c->i = 12345;
>
> p = c;
> printf("Integer is %d\n", p->i);
Should be legal, and should work. (And it appears it does.)
>
> v = c;
> p = (Parent*)v;
Potential problem here. I believe that the only thing the
standard guarantees you can do with void*'s is cast to them
and then cast them back to the original type.
v = c;
is, I believe, the same as
v = static_cast<void *>(c);
Following this with
Child *c2 = static_cast<Child *>(v);
is legal, and is guaranteed to give you
c2 == c;
But I believe that
p = static_cast<Parent *>(v);
is undefined behavior, and is not guaranteed to give
you what you want or expect.
> printf("Now the integer is %d\n", p->i);
And, indeed, you don't get what you expect.
> p = (Parent*)((char*)v + 4);
> printf("Shifting the base address yields: %d\n", p->i);
Now, why doesn't p = static_cast<Parent *>(v); work, and
why does your address-shift "fix" it.
Well, first, this is all implementation dependent, but my guess
is that a Parent* points directly to Parent::i, as Parent is POD
(" plain old data"). This might even be required by the standard,
as I think the standard makes some guarantees about POD's.
What does a Child* point to? It points to an instance of a Child
that, in addition to Child::i, also has a pointer to Child's v-table
(because Child has a virtual function). I'm guessing that the
implementation stores the v-table pointer before the Child::i,
and that the Child* points to the v-table pointer.
I think that this is all legitimate, and that the implementation is
allowed to do things this way if it so chooses.
Assuming that I've got this right, then the legal
Parent *p = static_cast<Parent *>(c);
(where c is a Child*) has to do the address-shift for you.
(Which it can, because when this line is compiled, the
compiler knows everything about Parent and Child, and
how they're laid out in memory.)
> ...
> Is this a bug in MinGW or just a rule in the C++ specification I'm
> unaware of?
I think MinGW is correct in this case in that it is allowed by
the standard to do things this way (but is not required to).
> Removing the "virtual" keyword removes the problem. I'm
> using "tdm-gcc-4.5.1"
This would make sense, because removing "virtual" from
Child makes Child also POD, and Child no longer has a v-table.
In fact, and instance of Child is, most likely, identical to an
instance of Parent, containing solely the int Child::i. So both
Parent* and Child* point to that int.
> ...
> Also, as long as I cast to the base class when inserting into the tree
> and then cast via the base class when taking things out of the tree
> there is no problem. Only when I cast directly to void* (when
> inserting) or the destination class (when querying) that the address
> shift is present.
Yes, this is legal. In effect, you are doing
Parent *p = static_cast<Parent *>(c);
void *v = static_cast<void *>(p);
Parent *p2 = static_cast<Parent *>(v);
Child *c2 = static_cast<Child *>(p2);
where c is a Child*.
> Regards,
> John-Philip
I hope I've got this right.
Good luck.
K. Frank
|