|
From: John-Philip T. <Joh...@ne...> - 2011-02-14 06:36:53
|
Hi,
Wow - thank you! Much more (and faster) than I expected. My
suspicion about the C++ standard is then correct (might be a good idea
for me to read it :-) ), and my "address-shift" simply jumps over the
implementation-specific v-table. In future I'll make sure that when
casting via void* I always cast back to the same type.
Regards,
John-Philip
On 14 February 2011 00:45, K. Frank <kfr...@gm...> wrote:
> 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
>
> ------------------------------------------------------------------------------
> The ultimate all-in-one performance toolkit: Intel(R) Parallel Studio XE:
> Pinpoint memory and threading errors before they happen.
> Find and fix more than 250 security defects in the development cycle.
> Locate bottlenecks in serial and parallel code that limit performance.
> http://p.sf.net/sfu/intel-dev2devfeb
> _______________________________________________
> MinGW-users mailing list
> Min...@li...
>
> This list observes the Etiquette found at
> http://www.mingw.org/Mailing_Lists.
> We ask that you be polite and do the same. Disregard for the list etiquette may cause your account to be moderated.
>
> _______________________________________________
> You may change your MinGW Account Options or unsubscribe at:
> https://lists.sourceforge.net/lists/listinfo/mingw-users
> Also: mailto:min...@li...?subject=unsubscribe
> #####################################################################################
> Scanned by MailMarshal - M86 Security's comprehensive email content security solution.
> Download a free evaluation of MailMarshal at www.m86security.com
> #####################################################################################
>
|