Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

sequence of inheritance from virtual base class?

227 views
Skip to first unread message

Ralf Fassel

unread,
May 14, 2013, 1:33:48 PM5/14/13
to
I have a problem with the sequence of inheritance:

// header1.h
class Base {
public:
Base() {}
virtual ~Base() {}
virtual void foo() = 0;
};

// header2.h
#include "header1.h"
class Unrelated1 {};
class Unrelated2 {};

class Derived1 : public Base, public Unrelated1, public Unrelated2 {
public:
Derived1() {}
~Derived1() {}
void foo() {}
};

class Derived2 : public Unrelated1, public Unrelated2, public Base {
public:
Derived2() {}
~Derived2() {}
void foo() {}
};


// main.cc
// g++ -o main main.cc
#include "header2.h"
int main() {
Derived1 d1;
Derived2 d2;
d1.foo(); // ok
d2.foo(); // crashes in big app
}

The problem seems to be the sequence of inheritance: if I inherit from
'Base' first (Derived1), the code seems to run ok. If I inherit from
'Base' last (Derived2), the code crashes reliably at runtime in a
seemingly unrelated code position (other class, other function, but
always the same).

The crash happens when Derived2 comes from one shared lib, Derived1 from
another. Calling a non-virtual function in Base seems ok, the crash
happens only when calling a virtual function in Base, so it looks to me
like a wrong lookup in the virtual function table.

This happens on Windows with Microsoft cl and on Linux with g++.

I've checked the FAQ for multiple inheritance issues but found nothing
related to this. It also does not matter whether I use
Derived2 : ... public Base ...
or
Derived2 : ... virtual public Base ...

Shouldn't this code be independent on the sequence of inheritance?

Thanks for any insights...
R'


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Öö Tiib

unread,
May 14, 2013, 5:23:49 PM5/14/13
to

On Tuesday, 14 May 2013 20:33:48 UTC+3, Ralf Fassel wrote:
> d1.foo(); // ok
> d2.foo(); // crashes in big app

The app you posted does not crash. You failed to make minimal example
that demonstrates the issue. Keep on looking. Undefined behavior that
crashes the app is in code that you did not post so we can not help
you.

Ralf Fassel

unread,
May 15, 2013, 6:42:13 AM5/15/13
to

* ᅵᅵ Tiib <oot...@hot.ee>
| On Tuesday, 14 May 2013 20:33:48 UTC+3, Ralf Fassel wrote:
| > d1.foo(); // ok
| > d2.foo(); // crashes in big app
| >
| The app you posted does not crash. You failed to make minimal
| example that demonstrates the issue. Keep on looking. Undefined
| behavior that crashes the app is in code that you did not post so we
| can not help you.

Just to confirm: you say the order of inheritance should NOT matter?

NB. I know that it does not crash, the code was meant to explain the
problem and a current work-around (which makes no sense IMHO).
valgrind does not show any problems up to the crash, and the debugger
ends up in a completely unrelated function when the crash happens (no
code path which leads to the function stack shown in gdb in the core
dump, single-threaded). If someone said "yes, the order of
inheritance DOES matter, cf. Standard x.y.z", I could stop
investigating further.

R'

Öö Tiib

unread,
May 15, 2013, 10:48:01 AM5/15/13
to
On Wednesday, 15 May 2013 12:50:04 UTC+3, Ralf Fassel wrote:
> * ᅵᅵ Tiib <oot...@hot.ee>
> | On Tuesday, 14 May 2013 20:33:48 UTC+3, Ralf Fassel wrote:
> | > d1.foo(); // ok
> | > d2.foo(); // crashes in big app
> | >
> | The app you posted does not crash. You failed to make minimal
> | example that demonstrates the issue. Keep on looking. Undefined
> | behavior that crashes the app is in code that you did not post so
> | we can not help you.
>
> Just to confirm: you say the order of inheritance should NOT matter?

Not for your case. The order of base classes and data members affects
in what order the sub-objects of object are constructed (and
destroyed). Your constructors and destructors do nothing and the
crash is not during construction or destruction so it should not
matter.

> NB. I know that it does not crash, the code was meant to explain the
> problem and a current work-around (which makes no sense IMHO).

Yes. The work-around achieves "lucky" circumstances. The defect is
still there it just does not manifest itself as regular crash. Regular
crashes are actually better since skilled specialist can find their
true cause quickly.

> valgrind does not show any problems up to the crash, and the
> debugger ends up in a completely unrelated function when the crash
> happens (no code path which leads to the function stack shown in gdb
> in the core dump, single-threaded). If someone said "yes, the order
> of inheritance DOES matter, cf. Standard x.y.z", I could stop
> investigating further.

Yes. You said yourself that it matters. Let me try to bring example
how it can matter?

For example if some "evil code" of yours is writing out of bounds into
stack where d1 and d2 reside then it may regularly damage a sub-object
of d2 and so cause a crash. By reordering the inheritance you move
the sub-object that crashes to different place in stack and the "evil
code" now still damages something else but the code does not crash
anymore.

Richard Damon

unread,
May 15, 2013, 10:52:01 AM5/15/13
to

On 5/15/13 6:42 AM, Ralf Fassel wrote:
>
> * Öö Tiib <oot...@hot.ee>
> | On Tuesday, 14 May 2013 20:33:48 UTC+3, Ralf Fassel wrote:
> | > d1.foo(); // ok
> | > d2.foo(); // crashes in big app
> | >
> | The app you posted does not crash. You failed to make minimal
> | example that demonstrates the issue. Keep on looking. Undefined
> | behavior that crashes the app is in code that you did not post so
> | we can not help you.
>
> Just to confirm: you say the order of inheritance should NOT matter?
>
> NB. I know that it does not crash, the code was meant to explain the
> problem and a current work-around (which makes no sense IMHO).
> valgrind does not show any problems up to the crash, and the
> debugger ends up in a completely unrelated function when the crash
> happens (no code path which leads to the function stack shown in gdb
> in the core dump, single-threaded). If someone said "yes, the order
> of inheritance DOES matter, cf. Standard x.y.z", I could stop
> investigating further.

{ Quoted signature removed -mod/we }

The order of inheritance should not matter for conforming code. A
likely issue is code that assumes &Derived == &Base and does invalid
casting on it.

Small nit, you do NOT have a "virtual base class" here, that term
refers to using virtual inheritance (using the virtual keyword in the
list of base classes), not inheriting from a polymorphic class with
virtual functions.

Ralf Fassel

unread,
May 16, 2013, 8:55:22 AM5/16/13
to
* Richard Damon <Ric...@Damon-Family.org>
| The order of inheritance should not matter for conforming code. A
| likely issue is code that assumes &Derived == &Base and does invalid
| casting on it.

That's indeed involved. I have two libraries, which need to pass
objects of class Base. For some reason I have to pass these via a
string representation. I do:

library 1:
Base *ptr = &Derived;
ptr->foo(); // ok
std::ostrstream ptrstr1 << (void*) ptr; // assume ptrstr1 is a
global variable

library 2:
std::istrstream ptrstr2(ptrstr1.str());
void *ptr;
ptrstr2 >> ptr;
Base *bptr = (Base*)ptr;
bptr->foo(); // crash if Base is not the first in inheritance list
of Derived

I have checked that the bptr in lib2 has the same value as ptr in
lib1. I thought that casting to/from void* using the same class in
both directions was ok, but maybe I'm missing something fundamental
here...

R'

goran...@googlemail.com

unread,
May 16, 2013, 9:11:55 AM5/16/13
to
{ Please do not insert blank lines when quoting. Inserted blank lines
in quoted section removed -mod/we }
First note that you're outside standard C++, who knows nothing about
shared libraries (or other kinds thereof). So it could be that your
issue is somehow related to the way code is built.

As for inheritance order, it does not matter, I don't think so. BTW,
if it did, your test code would also fail.

The fact code crashes elsewhere, in practice, indicates that the
problem is elsewhere. Note also that the code you've shown only reads
memory, so it's really improbable that it somehow provokes a crash
elsewhere.

I would try running with valgrind or the debug version of the MS CRT
and wait for memory corruption error reports - are you doing this? I
would also debug and look at the state of your variables at the place
of the crash - did you try that? (Can you simulate in debug builds,
BTW? If not, i would build an unoptimized release build and try to
have a look at the crash dump).

HTH,

Goran.

Stuart

unread,
May 18, 2013, 8:23:17 AM5/18/13
to
Am 16.05.13 14:55, schrieb Ralf Fassel:
> * Richard Damon <Ric...@Damon-Family.org>
> | The order of inheritance should not matter for conforming code. A
> | likely issue is code that assumes &Derived == &Base and does
> | invalid casting on it.
>
> That's indeed involved. I have two libraries, which need to pass
> objects of class Base. For some reason I have to pass these via a
> string representation. I do:
>
> library 1:
> Base *ptr = &Derived;
> ptr->foo(); // ok
> std::ostrstream ptrstr1 << (void*) ptr; // assume ptrstr1 is a
> global variable
>
> library 2:
> std::istrstream ptrstr2(ptrstr1.str());
> void *ptr;
> ptrstr2 >> ptr;
> Base *bptr = (Base*)ptr;
> bptr->foo(); // crash if Base is not the first in inheritance list
> of Derived
>
> I have checked that the bptr in lib2 has the same value as ptr in
> lib1. I thought that casting to/from void* using the same class in
> both directions was ok, but maybe I'm missing something fundamental
> here...

I ran the attached code, and it worked just fine. This indicates that
your marshalling code is probably not the source of the error, and
neither is the layout of your inheritance hierarchy.

The fact that you use operator& to take the address of Derived in your
code above leads me to believe that Derived may be a local variable
that may get destroyed after the function in library 1 has
finished. So it may be the case that library 2 receives a pointer that
is no longer valid. If you allocate Derived in the heap, you shouldn't
run into such problems.

Regards,
Stuart

#include <iostream>
#include <strstream>


class Base {
public:
Base() {}
virtual ~Base() {}
virtual void foo() = 0;
};

class Unrelated1 {};
class Unrelated2 {};

class Derived1 : public Base, public Unrelated1, public Unrelated2 {
public:
Derived1() {}
~Derived1() {}
void foo() {}
};

class Derived2 : public Unrelated1, public Unrelated2, public Base {
public:
Derived2() {}
~Derived2() {}
void foo() {}
};


int main() {
Derived2 d1;

Base *ptr = &d1;
ptr->foo(); // ok
std::ostrstream ptrstr1;
ptrstr1 << (void*) ptr; // assume ptrstr1 is a global variable

std::istrstream ptrstr2(ptrstr1.str());
void *ptr2;
ptrstr2 >> ptr2;
Base *bptr = (Base*)ptr2;
bptr->foo(); // crash if Base is not the first in inheritance list

// doesn't crash in this example.

Ralf Fassel

unread,
May 18, 2013, 4:50:22 PM5/18/13
to
* Richard Damon <Ric...@Damon-Family.org>
| The order of inheritance should not matter for conforming code. A
| likely issue is code that assumes &Derived == &Base and does invalid
| casting on it.

Ok, solved it. The reason for the crash was that the base class pointer
was not derived directly from the Derived pointer, but there was another
void* involved:

// virtual base functions omitted for brevity
class Unrelated {};
class Base {};
class Derived : public Unrelated, public Base {};

Base *p1 = &Derived; // ok

// push into hashtable and retrieve it back via void*
hashtable_put("key", &Derived);
void *v = hastable_get("key");

Base *p2 = (Base*) v; // wrong, vtable offset incorrect
Base *p3 = (Derived*) v; // ok, vtable offset correct

The pointer returned by the hashtable_get() needs first to get cast to
the original class pushed into the hashtable, otherwise the compiler
can't calculate the correct vtable offset (obvious once you grok it).

So my original observation that p2 and the original pointer were the
same was indeed the cause of the problem, they must *not* be the same
if Base is not the first base-class of Derived.

Another lesson learned. Had I looked up and posted the *real* code in
the first place instead of trying to simplify, I'm sure the error would
have been obvious (maybe even *before* posting :-)

Thanks
R'

Stuart

unread,
May 19, 2013, 3:04:51 AM5/19/13
to

On 05/18/13, Ralf Fassel wrote:
> * Richard Damon <Ric...@Damon-Family.org>
> | The order of inheritance should not matter for conforming code. A
> | likely issue is code that assumes &Derived == &Base and does
> | invalid casting on it.
>
> Ok, solved it. The reason for the crash was that the base class
> pointer was not derived directly from the Derived pointer, but there
> was another void* involved:
>
> // virtual base functions omitted for brevity
> class Unrelated {};
> class Base {};
> class Derived : public Unrelated, public Base {};
>
> Base *p1 = &Derived; // ok
>
> // push into hashtable and retrieve it back via void*
> hashtable_put("key", &Derived);
> void *v = hastable_get("key");
>
> Base *p2 = (Base*) v; // wrong, vtable offset incorrect
> Base *p3 = (Derived*) v; // ok, vtable offset correct
>
> The pointer returned by the hashtable_get() needs first to get cast
> to the original class pushed into the hashtable, otherwise the
> compiler can't calculate the correct vtable offset (obvious once you
> grok it).

That's not quite right. It is actually the pointer value that is
wrong, not the calculation of the vtable offset.

Note that:
Derived d;
Derived* ptrDerived = &d;
Base* ptrBase = &d;
if (static_cast<void*> (ptrBase) != static_cast<void*> (ptrDerived))
std::cout << "Addresses are different";
else
std::cout << "Addresses are same";

On my system I have to introduce at least one virtual method in class
Unrelated in order to get "Addresses are different"
printed. Apparently gcc reorders the base classes in such a way that
any base classes without virtual methods are put at higher addresses
(see example below).

> So my original observation that p2 and the original pointer were the
> same was indeed the cause of the problem, they must *not* be the
> same if Base is not the first base-class of Derived.

Not necessarily. The following code prints
Pointers to a are different.
Pointers to b are same.
on my system (I gave the classes some member variables so that we don't
get fooled by the empty-base-class-optimization):

class FirstBaseClass { int i;};
class SecondBaseClassWithoutVirtualMethods {int k;};
class SecondBaseClassWithVirtualMethods
{
int l;
virtual void foo () {}
};

class DerivedA : public FirstBaseClass,
public SecondBaseClassWithoutVirtualMethods {};
class DerivedB : public FirstBaseClass,
public SecondBaseClassWithVirtualMethods {};


int main ()
{
DerivedA a;
SecondBaseClassWithoutVirtualMethods* ptrSecondBaseA = &a;
if (static_cast<void*> (&a) == static_cast<void*> (ptrSecondBaseA))
std::cout << "Pointers to a are same.\n";
else
std::cout << "Pointers to a are different.\n";

DerivedB b;
SecondBaseClassWithVirtualMethods* ptrSecondBaseB = &b;
if (static_cast<void*> (&b) == static_cast<void*> (ptrSecondBaseB))
std::cout << "Pointers to b are same.\n";
else
std::cout << "Pointers to b are different.\n";
}

> Another lesson learned. Had I looked up and posted the *real* code
> in the first place instead of trying to simplify, I'm sure the error
> would have been obvious (maybe even *before* posting :-)

{ Quoted signature removed -mod }

Well, I learned something too. I didn't know that the compiler is
allowed to lay out the base class members in a different order than
the one that is given in the class definition. So if one has to
achieve some memory layout, one has to take special care if one of the
base classes contains virtual methods.

Regards,
Stuart

Bart van Ingen Schenau

unread,
May 19, 2013, 9:57:33 AM5/19/13
to
On Sun, 19 May 2013 01:04:51 -0600, Stuart wrote:

> Well, I learned something too. I didn't know that the compiler is
> allowed to lay out the base class members in a different order than
> the one that is given in the class definition. So if one has to
> achieve some memory layout, one has to take special care if one of
> the base classes contains virtual methods.

Actually, even without virtual functions you can not count on any
order of the base-class sub objects. The standard explicitly has made
that order unspecified.

And it gets worse (although slightly less so with C++11). If you have
this structure:

struct X {
public: int i;
private: char c;
public: float f;
};

then a C++98/C++03 compiler is free to rearrange all three members in
any order it likes (members may be reordered if there is an
intervening access specifier).
A C++11 compiler must keep i and f in the current order, but it can
move c about at will (members with different access levels may be
reordered relative to each other. members with the same access level
must keep their declared order).

Your best bet to create a structure with a defined layout is to
declare it as a C-compatible structure (no base classes, no access
specifiers, no virtual members).

{ Quoted signature removed -mod }

Bart v Ingen Schenau

Francis Glassborow

unread,
May 19, 2013, 10:01:04 AM5/19/13
to
On 19/05/2013 08:04, Stuart wrote:

> Well, I learned something too. I didn't know that the compiler is
> allowed to lay out the base class members in a different order than
> the one that is given in the class definition. So if one has to
> achieve some memory layout, one has to take special care if one of
> the base classes contains virtual methods.

{ Quoted signature removed -mod }

Well a class that includes at least one virtual function is likely to
be more vulnerable, but, IIUC, the implementation is at liberty to lay
out a derived class' base classes anyway it likes. There are, IIRC,
some limitations on the relationship between a pointer to the derived
class and a pointer to the first base class in the declaration of the
derived class.

Multiple inheritance (particularly when it involves virtual bases) is
quite tough to implement. Indeed, when virtual bases (rather than just
polymorphic base classes) necessitates that the layout of base class
sub-objects may vary down the inheritance tree. In simple terms, do
not make assumptions about the layout of classes that include the
potential for multiple inheritance.

Regards
Francis

Wil Evers

unread,
May 19, 2013, 7:39:57 PM5/19/13
to
Francis Glassborow wrote:

> On 19/05/2013 08:04, Stuart wrote:
>
>> Well, I learned something too. I didn't know that the compiler is
>> allowed to lay out the base class members in a different order than
>> the one that is given in the class definition. So if one has to
>> achieve some memory layout, one has to take special care if one of
>> the base classes contains virtual methods.

> Well a class that includes at least one virtual function is likely
> to be more vulnerable, but, IIUC, the implementation is at liberty
> to lay out a derived class' base classes anyway it likes. There are,
> IIRC, some limitations on the relationship between a pointer to the
> derived class and a pointer to the first base class in the
> declaration of the derived class.

Are you sure? As far as I know, if we have...

class Base { /* ... */ };
class Derived : public Base { /* ... */ };

...then for a Derived object, the compiler is free to either use a
layout in which the Base part comes first, or one in which the Derived
part comes first (which would not be very practical, but that's beside
the point).

I think the lesson to learn from this thread is that a void pointer
obtained from the address of an X must not be cast back to a pointer
to some other type, even if that other type is closely related to X.

Regards,

- Wil

Francis Glassborow

unread,
May 20, 2013, 6:16:16 AM5/20/13
to
static_cast<Base> should convert the pointer if necessary,
reinterpret_cast<Base> can fail.

Francis

Daniel Krügler

unread,
May 20, 2013, 8:40:46 AM5/20/13
to
I don't think that static_cast versus reinterpret_cast is relevant in
the here discussed conversion case. The reason for the observed
problem really is (as others have said in different words) that the
language doesn't specify that the conversion sequence (all by means of
static_cast)

D* d1 -> void* v -> B* b -> D* d2

where B* and D* do have an effective offset different from zero, will
return an address value d2 that is equal to the original address value
d1. The constraint is expressed in 5.2.9 p13:

"A prvalue of type “pointer to cv1 void” can be converted to a prvalue
of type “pointer to cv2 T,” [..] If the original pointer value
represents the address A of a byte in memory and A satisfies the
alignment requirement of T, then the resulting pointer value
represents the same address as the original pointer value, that is,
A. The result of any other such pointer conversion is unspecified. A
value of type pointer to object converted to “pointer to cv void” and
back, possibly with different cv-qualification, shall have its
original value."

The behavior of above sequence is undefined, because the assumption
that b points to the same address value as the original address value
of d1 is invalid.

HTH & Greetings from Bremen,

Daniel Krügler

Wil Evers

unread,
May 20, 2013, 3:45:13 PM5/20/13
to
Francis Glassborow wrote:
> On 20/05/2013 00:39, Wil Evers wrote:
>> Francis Glassborow wrote:
[...]
>>> IIUC, the implementation is at liberty to lay out a derived class'
>>> base classes anyway it likes. There are, IIRC, some limitations on
>>> the relationship between a pointer to the derived class and a
>>> pointer to the first base class in the declaration of the derived
>>> class.
>>
>> Are you sure? As far as I know, if we have...
>>
>> class Base { /* ... */ };
>> class Derived : public Base { /* ... */ };
>>
>> ...then for a Derived object, the compiler is free to either use a
>> layout in which the Base part comes first, or one in which the
>> Derived part comes first (which would not be very practical, but
>> that's beside the point).
>>
>> I think the lesson to learn from this thread is that a void pointer
>> obtained from the address of an X must not be cast back to a
>> pointer to some other type, even if that other type is closely
>> related to X.
>
> static_cast<Base> should convert the pointer if necessary,
> reinterpret_cast<Base> can fail.

I'm not sure I understand. Do you mean that, for the hierarchy
above...

{
Derived d;
Base *bp = &d;
void *vp = bp;
Derived *dp = static_cast<Derived *>(vp);
assert(dp == &d);
}

...must work? I don't think that's guaranteed, because vp is
obtained from the address of a Base, and then cast back to the address
of a Derived. For the hypothetical implementation that lays out the
Derived part before the Base part, conversions between a Base * and a
Derived * would need a pointer adjustment; I don't think such
adjustments are required to be applied when converting to or from a
void *.

In contrast, I would expect...

{
Derived d;
Base *bp1 = &d;
void *vp = bp1;
Base *bp2 = static_cast<Base *>(vp);
Derived *dp = static_cast<Derived *>(bp2);
assert(dp == &d);
}

...to work, because vp is obtained from a the address of a Base, and
then cast back to the address of a Base again. For the other
conversions, the compiler has all the information it needs to perform
any required adjustments.

I checked the standard, but I couldn't find anything that suggests
the first example should work. Am I missing something?

Regards,

- Wil

Francis Glassborow

unread,
May 20, 2013, 3:51:34 PM5/20/13
to
On 20/05/2013 13:40, Daniel Kr�gler wrote:
>
> Am 20.05.2013 12:16, schrieb Francis Glassborow:
>> On 20/05/2013 00:39, Wil Evers wrote:
>>> I think the lesson to learn from this thread is that a void pointer
>>> obtained from the address of an X must not be cast back to a
>>> pointer to some other type, even if that other type is closely
>>> related to X.
>>
>> static_cast<Base> should convert the pointer if necessary,
>> reinterpret_cast<Base> can fail.
>
> I don't think that static_cast versus reinterpret_cast is relevant in
> the here discussed conversion case. The reason for the observed
> problem really is (as others have said in different words) that the
> language doesn't specify that the conversion sequence (all by means of
> static_cast)
>
> D* d1 -> void* v -> B* b -> D* d2
>
> where B* and D* do have an effective offset different from zero, will
> return an address value d2 that is equal to the original address value
> d1. The constraint is expressed in 5.2.9 p13:
>
> "A prvalue of type �pointer to cv1 void� can be converted to a prvalue
> of type �pointer to cv2 T,� [..] If the original pointer value
> represents the address A of a byte in memory and A satisfies the
> alignment requirement of T, then the resulting pointer value
> represents the same address as the original pointer value, that is,
> A. The result of any other such pointer conversion is unspecified. A
> value of type pointer to object converted to �pointer to cv void� and
> back, possibly with different cv-qualification, shall have its
> original value."
>
> The behavior of above sequence is undefined, because the assumption
> that b points to the same address value as the original address value
> of d1 is invalid.

Agreed but it is the step from Base* to Derived* that is the problem. I
was merely addressing the Derived* to Base* conversion which AFAIK was
and remains valid if done via a static_cast<>
Consider:

class Base {
// whatever
};
class Derived: public Base (
//whatever
};

void bar(Base *);

void foo(){
Base * b1_ptr = new Derived;
Base * b2_ptr = new Base;
bar(b1_ptr); // possible problem with slicing
bar(b2_ptr); // no problems
}

Do we now have a problem not just with slicing, but fundamentally that
this what is passed to bar may not even be a Base* ?

I suspect that we (maybe just me) have got confused. I know that we
cannot cast back from Base* to Derived*. However if we use a
static_cast<> we will obtain the address of the Base part of Derived
even if that is different to the address of the derived object.

The ability to go there and back is limited to polymorphic types and
using dynamic_cast<>. If the implementation were required to place the
Base object at the address of the derived then reinterpret_cast<> would
work. But that has never been the case. (I can remember bringing an
otherwise excellent author to task for that misunderstanding back in the
90s)

Anyway the main point is that other from the extended (by C++11) concept
of Plain Old Data types we have few guarantees about layout though C++11
does require that data with the same access specifier shall be placed in
order though possibly interleaved with data with a different access
specifier (and presumably that interleaving continues to be available
for base class data in a derived class:

struct Base {
int i;
char c;
double d;
};

struct Derived public Base {
int j;
char c1;
double d1;
};

?? Derived can be laid out as:

j, i, c, c1, d, d1

??

Joe keane

unread,
May 21, 2013, 4:15:22 PM5/21/13
to
In article <knan10$67p$1...@dont-email.me>, Wil Evers <bou...@dev.null> wrote:
>I think the lesson to learn from this thread is that a void pointer
>obtained from the address of an X must not be cast back to a pointer
>to some other type, even if that other type is closely related to X.

What we know is that this will work:

Base *bp1;
void *vp;
Base *bp2;

...
vp = (void *) bp1;
bp2 = (Base *) vp;

also

Derived *dp1;
void *vp;
Derived *dp2;

...
vp = (void *) dp1;
dp2 = (Derived *) vp;

We also know that this will work:

Derived *dp;
Base *bp;

...
bp = (Base *) dp;

And -maybe- there is some way to go backward:

Base *bp;
Derived *dp;

...
dp = ???_cast<Derived *> bp;

You just can't mix them up!

So don't lie to the compiler.


There is an exception:

struct bar
{
int bar_a;
};

struct foo
{
struct bar foo_a;
int foo_b;
};

struct foo *fp;
struct bar *bp;

...
bp = (struct foo *) fp;
...
fp = (struct bar *) bp;

But it is some C rule...

1) no base classes, cause that's not C
2) no virtual methods, cause that's not C
3) no virtual inheritance, cause that's not C

So don't try it with a 'complicated' class.

It won't work, or if it does, it won't work some time.

Bart van Ingen Schenau

unread,
May 21, 2013, 4:33:06 PM5/21/13
to
On Mon, 20 May 2013 12:51:34 -0700, Francis Glassborow wrote:

> On 20/05/2013 13:40, Daniel Krügler wrote:
>>
>> Am 20.05.2013 12:16, schrieb Francis Glassborow:
>>> On 20/05/2013 00:39, Wil Evers wrote:
>>>> I think the lesson to learn from this thread is that a void pointer
>>>> obtained from the address of an X must not be cast back to a pointer
>>>> to some other type, even if that other type is closely related to X.
>>>
>>> static_cast<Base> should convert the pointer if necessary,
>>> reinterpret_cast<Base> can fail.
>>
>> I don't think that static_cast versus reinterpret_cast is relevant in
>> the here discussed conversion case. The reason for the observed problem
>> really is (as others have said in different words) that the language
>> doesn't specify that the conversion sequence (all by means of
>> static_cast)
>>
>> D* d1 -> void* v -> B* b -> D* d2
>>
>> where B* and D* do have an effective offset different from zero, will
>> return an address value d2 that is equal to the original address value
>> d1. The constraint is expressed in 5.2.9 p13:
>>
>> "A prvalue of type “pointer to cv1 void” can be converted to a prvalue
>> of type “pointer to cv2 T,” [..] If the original pointer value
>> represents the address A of a byte in memory and A satisfies the
>> alignment requirement of T, then the resulting pointer value represents
>> the same address as the original pointer value, that is, A. The result
>> of any other such pointer conversion is unspecified. A value of type
>> pointer to object converted to “pointer to cv void” and back, possibly
>> with different cv-qualification, shall have its original value."
>>
>> The behavior of above sequence is undefined, because the assumption
>> that b points to the same address value as the original address value
>> of d1 is invalid.
>
> Agreed but it is the step from Base* to Derived* that is the problem.

It was only a problem because the source pointer did, in fact, *not*
refer to a Base (sub-)object.

> I was merely addressing the Derived* to Base* conversion which AFAIK was
> and remains valid if done via a static_cast<> Consider:
>
> class Base {
> // whatever
> };
> class Derived: public Base (
> //whatever
> };
>
> void bar(Base *);
>
> void foo(){
> Base * b1_ptr = new Derived;
> Base * b2_ptr = new Base;
> bar(b1_ptr); // possible problem with slicing

As bar() takes its argument as a pointer, there is no risk at all of
slicing. The only possible "problem" is that, if Base does not have any
virtual functions, then bar() can't take any advantage of what Derived
has to offer.

> bar(b2_ptr); // no problems
> }
>
> Do we now have a problem not just with slicing, but fundamentally that
> this what is passed to bar may not even be a Base* ?

No, there are no problems of any kind in the code you presented.
The implicit conversion from Derived (reference or pointer) to Base
(reference or pointer) and a static_cast in either direction must take
the offset of the Base sub-object within Derived into account.

This requirement does not hold for reinterpret_cast, or if the conversion
goes through an intermediary type (such as void*).
It was this latter aspect of which the code from the OP ran afoul. It
converted a Derived* to a Base* through a void* in one way, and directly
in the reverse way. This created havoc with a nonzero offset of the Base
sub-object.

>
> I suspect that we (maybe just me) have got confused. I know that we
> cannot cast back from Base* to Derived*.

Actually, if you know that the pointer refers to a Base sub-object, then
a static_cast to Derived (pointer or reference) is completely well-
defined (unless they removed that from C++11, which I doubt).

> However if we use a
> static_cast<> we will obtain the address of the Base part of Derived
> even if that is different to the address of the derived object.
>
> The ability to go there and back is limited to polymorphic types and
> using dynamic_cast<>.

It is not as black-and-white as you paint it here, but dynamic_cast<>
*does* give you more guarantees if you made an error in remembering which
most-derived class your pointer actually refers to.
You *can* make the conversion with static_cast<>, but you are on your own
if you screw up (as in, UB).

Bart v Ingen Schenau
0 new messages