sizeof most-derived-class

M

munna

Considering the classic diamond problem in where we have this
relationship,
A
/ \
B C
\ /
D
and define our classes like the following:

class A{
public:
virtual void f1(){}
};
class B: public virtual A{
public:
void f1(){}
};
class C: public virtual A{
public:
void f1(){}
};
class D: public B, public C{
public:
void f1(){}
};

Specifically how the memory-layout for class D-object would be laid
out (in practice). Or is it so that since the standard doesn't
mandates any specific layout, compilers are free to have it at their
discretion? What i understand is that size of class A would be 4 bytes
(on a 32-bit architecture), sizeof B and C would be 8 bytes (one vptr
for B/C and one pointer to virtual base class A). How exactly would a
D object look like?
 
T

tonydee

Considering the classic diamond problem in where we have this
relationship,
                                                   A
                                                 /    \
                                               B     C
                                                \     /
                                                   D
 and define our classes like the following:

class A{
public:
     virtual void f1(){}};

class B: public virtual A{
public:
     void f1(){}};

class C: public virtual A{
public:
      void f1(){}};

class D: public B, public C{
public:
      void f1(){}

};

 Specifically how the memory-layout for class D-object would be laid
out (in practice). Or is it so that since the standard doesn't
mandates any specific layout, compilers are free to have it at their
discretion? What i understand is that size of class A would be 4 bytes
(on a 32-bit architecture), sizeof B and C would be 8 bytes (one vptr
for B/C and one pointer to virtual base class A). How exactly would a
D object look like?

As Victor says, this isn't specified in the Standard, but typically a
compiler will want D to be a concatenation of a B and C object, as it
needs to be able to pass a pointer-to-(some-part-of)-D to functions
accepting B* or C*, and such functions aren't going to want to perform
some run-time check to see if parts of the B or C object are laid out
in a different layout used by some derived classes. So, expect sizeof
D to be sizeof B + sizeof C.

Cheers,
Tony
 
J

Joshua Maurice

As Victor says, this isn't specified in the Standard, but typically a
compiler will want D to be a concatenation of a B and C object, as it
needs to be able to pass a pointer-to-(some-part-of)-D to functions
accepting B* or C*, and such functions aren't going to want to perform
some run-time check to see if parts of the B or C object are laid out
in a different layout used by some derived classes.  So, expect sizeof
D to be sizeof B + sizeof C.

Virtual inheritance is involved. I would specifically expect not that.
For MSVC 2008 on a 32 bit build, I happened to get
sizeof A == 4
sizeof B == 8
sizeof C == 8
sizeof D == 12

WHAT FOLLOWS IS NOT PORTABLE. DO NOT RELY ON IT IN PRODUCTION CODE.

All D objects need to contain a B subobject, a C subobject, and
exactly one A subobject. As such, it cannot simply put A's members at
offset 0 in the B subobject and offset 0 in the C suboject. (Think
about it.) Normally, to convert between a base class pointer and a
derived class pointer, just a compile time known offset is added (0
for single inheritance, and zero and nonzero for multiple
inheritance). For virtual inheritance, the offset to a base class
subobject is no longer a compile time constant, and it must be looked
up at runtime, hence this extra "stuff" in the B, C, and D objects.
 
T

tonydee

Virtual inheritance is involved. I would specifically expect not that.
For MSVC 2008 on a 32 bit build, I happened to get
sizeof A == 4
sizeof B == 8
sizeof C == 8
sizeof D == 12

For SunC++ 5.8 and g++ 3.4.3 I get 4, 4, 4 and 8.
WHAT FOLLOWS IS NOT PORTABLE. DO NOT RELY ON IT IN PRODUCTION CODE.

All D objects need to contain a B subobject, a C subobject, and
exactly one A subobject. As such, it cannot simply put A's members at
offset 0 in the B subobject and offset 0 in the C suboject. (Think
about it.)

Agreed, but B and C may both embed a pointer to the single A.
For virtual inheritance, the offset to a base class
subobject is no longer a compile time constant, and it must be looked
up at runtime, hence this extra "stuff" in the B, C, and D objects.

True... via the pointer.

Cheers,
Tony
 
M

munna

Agreed, but B and C may both embed a pointer to the single A.

If both B and C contains an embedded pointer to the shared base-class
object, the size would have been 16 for D (which is not the case!)

The reason my understanding got all muddled up : when i was going
through the text mentioned in the thread above "Inside Object Model",
where it says that the memory layout of class B would be so that it
contains

1. vptr-b pointing to B's vtable (4 bytes)
2. pointer to the virtual base class (4 bytes)

and hence the size of B (and similarly for C) would be 8 bytes. But,
while composing the D object, the layout is such that it contains two
portions viz. the shared/invariant region (marked by the shared base
sub-object) and both B and C contain a pointer to it! ( Chapter 3,
section 3.4, figure 3.5a) where it says something like this

1. for class A:
 
T

tonydee

[given...

struct A { virtual void f() { } };
struct B : public virtual A { void f() { } };
struct C : public virtual A { void f() { } };
struct D : public B, public C { void f() { } };
]
If both B and C contains an embedded pointer to the shared base-class
object, the size would have been 16 for D (which is not the case!)

The reason my understanding got all muddled up : when i was going
through the text mentioned in the thread above "Inside Object Model",
where it says that the memory layout of class B would be so that it
contains

1. vptr-b pointing to B's vtable (4 bytes)
2. pointer to the virtual base class (4 bytes)

and hence the size of B (and similarly for C) would be 8 bytes.

But the virtual function from A is the only virtual function in B, why
would a distinct "1." be required? Seems Sun and GNU compilers are
smart enough to consolidate the above.

Same goes for D really, but due to the reason I mentioned before -
wanting discrete, contiguous embedded B and C objects to pass to
functions expecting them, it's easier to have two pointers to the
(presumably same) virtual dispatch table.

[snip]
So obviously something here is at contradiction!!!

Hope that clarifies things.

Cheers,
Tony
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top