[comp.lang.c++] Multiple inheritance with virtual bases

hagiwara@zuken.co.jp (Kazuyuki Hagiwara) (10/18/90)

I am using AT&T C++ version 2.0.  Following is the simple test of
multipile inheritance with virtual base classes. Do anyone know
the reason why sizeof(D) is so large ?  I think sizeof(D) should be
only 1000 bytes and some pointer overhead.

----------------------------- mibug.C  {  ----------------
#include <iostream.h>

struct A {
	char		avar0[1000];
};

struct B : virtual public A {
	int		bvar;
};

struct C : virtual public A {
	int		cvar;
};

struct D : virtual public B, virtual public C {
	int		dvar;
};

main()
{
	A	a1;
	B	b1;
	C	c1;
	D	d1;

	cout << "sizeof A: " << sizeof(A) << endl;
	cout << "sizeof B: " << sizeof(B) << endl;
	cout << "sizeof C: " << sizeof(C) << endl;
	cout << "sizeof D: " << sizeof(D) << endl;
}
----------------------------- } mibug.C  ----------------
----- output of mibug { -------
sizeof A: 1000
sizeof B: 1008
sizeof C: 1008
sizeof D: 2032
----- output of mibug } -------
--
Kazuyuki Hagiwara @ Zuken, Inc.  Yokohama, Japan (hagiwara@zuken.co.jp)

sking@nowhere.uucp (Steven King) (10/22/90)

In article <HAGIWARA.90Oct18105013@rd14.zuken.co.jp> hagiwara@zuken.co.jp (Kazuyuki Hagiwara) writes: 
>
>I am using AT&T C++ version 2.0.  Following is the simple test of
>multipile inheritance with virtual base classes. Do anyone know
>the reason why sizeof(D) is so large ?  I think sizeof(D) should be
>only 1000 bytes and some pointer overhead.
>
>----------------------------- mibug.C  {  ----------------
>#include <iostream.h>
>
>struct A {
>	char		avar0[1000];
>};
>
>struct B : virtual public A {
>	int		bvar;
>};
>
>struct C : virtual public A {
>	int		cvar;
>};
>
>struct D : virtual public B, virtual public C {
>	int		dvar;
>};

	The definitions of B and C each have space for A allocated within them;
thus the size of D is sizeof B ( which is sizeof A + pointer overhead ) +
sizeof C ( sizeof A + pointer overhead ) + more pointer overhead. 

	A little thought on how virtual bases are implemented should reveal 
why this is, tho' I'm not sure that a smarter Cfront might only need to alloc
space for it once. 


-- 
 sking@nowhere			      | C++ is to C, what espresso is to coffee;
..!cs.utexas.edu!ut-emx!nowhere!sking | Definitely an acquired taste, but once
				      | you have acquired that taste, you cant
				      | go back ...

steve@taumet.com (Stephen Clamage) (10/22/90)

sking@nowhere.uucp (Steven King) writes:

|In article |HAGIWARA.90Oct18105013@rd14.zuken.co.jp> hagiwara@zuken.co.jp (Kazuyuki Hagiwara) writes: 
|>
[ ... ]
|>struct A {
|>	char		avar0[1000];
|>};
|>struct B : virtual public A {
|>	int		bvar;
|>};
|>struct C : virtual public A {
|>	int		cvar;
|>};
|>struct D : virtual public B, virtual public C {
|>	int		dvar;
|>};

|	The definitions of B and C each have space for A allocated within them;
|thus the size of D is sizeof B ( which is sizeof A + pointer overhead ) +
|sizeof C ( sizeof A + pointer overhead ) + more pointer overhead. 

This is not correct.  Exactly one instance of a virtual base class exists
in an object by definition.  Class A is declared to be virtual in all uses
of it, so it is an error for there to be more than one copy.  Let us hope
that the error in this case is in the allocation, and that only one copy
is ever referred to by generated code.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

vaughan@mcc.com (Paul Vaughan) (10/22/90)

The discussion between Kazuyuki Hagiwara and Steven King about the
sizes of muliply and virtually derived objects is curious indeed.
Here is what GNU C++ does, followed by cfront 2.0 (Sun C++).

#include <stream.h>

struct A {
	char		avar0[1000];
};

struct B : virtual public A {
	int		bvar;
};

struct C : virtual public A {
	int		cvar;
};

struct D : virtual public B, virtual public C {
	int		dvar;
};

main() {
  cout << "A " << sizeof(A) 
    << " B " << sizeof(B) 
      << " C " << sizeof(C) 
	<< " D " << sizeof(D) << "\n";
}

bash$ g++  -o trash trash.cc
bash$ trash
A 1000 B 1008 C 1008 D 1028                            <= reasonable
bash$ CC  -o trash trash.cc
CC  trash.cc:
cc   -o /d2/prism/prism/src/sim/src/trash -I/net/sunspot/usr/cadsw/CC/sun4/incl  trash.c  -L/net/sunspot/usr/cadsw/CC/sun4/ -lC
bash$ trash
A 1000 B 1008 C 1008 D 2032                            <= unreasonable


 Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639
 Box 200195, Austin, TX 78720  | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan

jamiller@hpcupt1.cup.hp.com (Jim Miller) (10/25/90)

IMHO: the 2K size is a bug.


E&S's ARM, 10.1: "

      class V { /* ... */ };
      class A : virtual public V { /* ... */ };
      class B : virtual public V { /* ... */ };
      class C : public A, public B { /* ... */ };

   Here class C has only one sub-object of class V. "


   jim miller
   jamiller@hpmpeb7.cup.hp.com
   (a.k.a James A. Miller; Jim the JAM; stupid; @!?$$!; ... )
   Anything I say will be used against me ...
   But my company doesn't know or approve or condone anything of mine here.

Reid Ellis <rae@gpu.utcs.toronto.edu> (10/25/90)

The source being discussed is appended at the end of this article
after a linefeed.  I noticed that the struct 'D' does not need to use
"virtual" for deriving from B and C [although it shouldn't matter for
this code] and tried removing them.

Under Apple's MPW C++ compiler, I get the previously mentioned cfront
output:
A 1000 B 1008 C 1008 D 2032

If, however, I change the line
"struct D : virtual public B, virtual public C {"
to
"struct D : public B, public C {"
then I get the more expected result of:
A 1000 B 1008 C 1008 D 1020

Hope this pins the problem down a bit more.  And if you need to, use
this as a workaround.
					Reid
Original source follows:

#include <stream.h>

struct A {
	char		avar0[1000];
};

struct B : virtual public A {
	int		bvar;
};

struct C : virtual public A {
	int		cvar;
};

struct D : virtual public B, virtual public C {
	int		dvar;
};

main() {
  cout << "A " << sizeof(A) 
    << " B " << sizeof(B) 
      << " C " << sizeof(C) 
	<< " D " << sizeof(D) << "\n";
}
--
Reid Ellis  264 Broadway Avenue, Toronto ON, M4P 1V9               Canada
rae@gpu.utcs.toronto.edu || rae%alias@csri.toronto.edu || +1 416 487 1383

shankar@hpclscu.HP.COM (Shankar Unni) (10/26/90)

> IMHO: the 2K size is a bug.
> E&S's ARM, 10.1: "
> 
>       class V { /* ... */ };
>       class A : virtual public V { /* ... */ };
>       class B : virtual public V { /* ... */ };
>       class C : public A, public B { /* ... */ };
> 
>    Here class C has only one sub-object of class V. "

Yes, and if you feed this case to cfront, it gives the "right" answer
(1020).

It's when C derives from a virtual A and a virtual B that things get
interesting. I think it's a cfront bug, too.
-----
Shankar Unni.
HP Calif. Language Lab.

sking@nowhere.uucp (Steven King) (10/27/90)

In article <488@taumet.com> steve@taumet.com (Stephen Clamage) writes:

>sking@nowhere.uucp (Steven King) writes:
>
>|In article |HAGIWARA.90Oct18105013@rd14.zuken.co.jp> hagiwara@zuken.co.jp (Kazuyuki Hagiwara) writes: 
>|>
>[ ... ]
>|>struct A {
>|>	char		avar0[1000];
>|>};
>|>struct B : virtual public A {
>|>	int		bvar;
>|>};
>|>struct C : virtual public A {
>|>	int		cvar;
>|>};
>|>struct D : virtual public B, virtual public C {
>|>	int		dvar;
>|>};
>
>|	The definitions of B and C each have space for A allocated within them;
>|thus the size of D is sizeof B ( which is sizeof A + pointer overhead ) +
>|sizeof C ( sizeof A + pointer overhead ) + more pointer overhead. 
>
>This is not correct.  Exactly one instance of a virtual base class exists
>in an object by definition.  Class A is declared to be virtual in all uses
>of it, so it is an error for there to be more than one copy.  Let us hope
>that the error in this case is in the allocation, and that only one copy
>is ever referred to by generated code.

     ( sigh )

    It is correct as a simple test and examination of the C output would have
 shown. To wit:


struct A {	/* sizeof A == 1000 */
	char avar0__1A [1000];
};

struct B {	/* sizeof B == 1008 */
	int bvar__1B ;
	struct A *PA;
	struct A OA;
};

struct C {	/* sizeof C == 1008 */
	int cvar__1C ;
	struct A *PA;
	struct A OA;
};

struct D {	/* sizeof D == 2032 */
	int dvar__1D ;
	struct C *PC;
	struct B *PB;
	struct A *PA;
	struct C OC;
	struct B OB;
};

   Each class has space allocated for the virtual base, and has a pointer to
 the virtual base it actually uses. The constructors are passed a pointer to
 the virtual base they are to use. Tf the pointer is NULL, they initialize it,
 call the constructor for the virtual base, and pass it to the constructors
 for the classes they are derived from. Its pretty straight forward, tho' the
 code generated can be rather dense....



   and now for a slight flame....

   Am I the only person using Cfront who checks the C output when there is
a question about what it is doing?

   If one is using Cfront and has a question about what is going on 
 (especially in one's own code), there is no excuse for not examining the C
 output. If the lack of formatting bothers you, then run it thru cb ( I 
 edited my CC script to do it automatically for me ) 


   Or are there a bunch of C weenies out there who cant stand the sight of
 raw C code ;-}

   regards,
-- 
sking@nowhere			      | C++ is to C, what espresso is to
..!cs.utexas.edu!ut-emx!nowhere!sking | coffee; Definitely an acquired taste,
				      | but once you have acquired that taste,
				      | you cant go back ...

dans@microsoft.UUCP (Dan SPALDING) (10/28/90)

In article <488@taumet.com> steve@taumet.com (Stephen Clamage) writes:
>sking@nowhere.uucp (Steven King) writes:
>
>|In article |HAGIWARA.90Oct18105013@rd14.zuken.co.jp> hagiwara@zuken.co.jp (Kazuyuki Hagiwara) writes: 
>
>This is not correct.  Exactly one instance of a virtual base class exists
>in an object by definition.  Class A is declared to be virtual in all uses
>of it, so it is an error for there to be more than one copy.  Let us hope
>that the error in this case is in the allocation, and that only one copy
>is ever referred to by generated code.
>-- 
>
>Steve Clamage, TauMetric Corp, steve@taumet.com

while that is technically true, cfront generates structure defn's that are
not in any way optimal.  The space for both the A part of B and C are in B
and C, and the class D includes both of these structures and a pointer to
the shared A (which is one of the ones in B or C).  So, from the user's 
point of view, there is only one A, but the space for two of them is
consumed.  Not very neat is it?

here is an example of mi.cxx and it's resultant mi.c from Glock 2.0:

mi.cxx:

class A {
	char _rgb[1000];
	};
class B : public virtual A {
	int _w;
	};
class C : public virtual A {
	int _w;
	};
class D : public virtual B, public virtual C {
	int _w;
	};

main () {
	D * pd;
	};



mi.c:


#line 1 "mi.cxx"

/* << cfxx :-  2.0 Msoft a >> */
/* < mi.cxx > */
static void __cvp30_support(void)
{}
void * __vec_new ( void * , int , int , void * )
#line 1
;

#line 1
void __vec_delete ( void * , int , int , void * , int
#line 1
, int ) ;
typedef int (*__vptp)();
struct __mptr {short d; short i; __vptp f; };
static void _cdecl _STI();
static void _cdecl _STD();

#line 1
struct A;
struct A {	/* sizeof A == 1000 */

char _rgb__1A [ 1000] ;
};
struct B;

struct B {	/* sizeof B == 1006 */

int _w__1B ;
struct A * PA;
struct A OA;
};

#line 1
extern void * __nw__FUi ( unsigned int ) ;
struct C;

#line 7
struct C {	/* sizeof C == 1006 */

int _w__1C ;
struct A * PA;
struct A OA;
};
struct D;

#line 9

#line 6

#line 10
struct D {	/* sizeof D == 2026 */

int _w__1D ;
struct C * PC;
struct B * PB;
struct A * PA;
struct C OC;
struct B OB;
};

#line 14
int main ( void ) { { 
struct D * pd ;
}
} 
#line 16

/* the end */




------------------------------------------------------------------------
dan spalding -- microsoft systems/languages -- microsoft!dans

"there has not been a word invented that would convey my indifference to
that remark." - paraphrase from hawkeye pierce
------------------------------------------------------------------------

sking@nowhere.uucp (Steven King) (10/29/90)

   Unfortunately, my newsfeed is about 4 days behind the rest of the world,
 and after getting somewhat caught up on my reading of this group, I thought
 I had stuck my foot in it... However, after further thought, I still believe
 that my interpretation of what Cfront is doing and why it is doing what it
 is doing is correct =-}

In article <12342@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes:
>The discussion between Kazuyuki Hagiwara and Steven King about the
>sizes of muliply and virtually derived objects is curious indeed.
>Here is what GNU C++ does, followed by cfront 2.0 (Sun C++).

	[ code already repeated several times not shown ]

>bash$ g++  -o trash trash.cc
>bash$ trash
>A 1000 B 1008 C 1008 D 1028                            <= reasonable
>bash$ CC  -o trash trash.cc
>CC  trash.cc:
>cc   -o /d2/prism/prism/src/sim/src/trash -I/net/sunspot/usr/cadsw/CC/sun4/incl  trash.c  -L/net/sunspot/usr/cadsw/CC/sun4/ -lC
>bash$ trash
>A 1000 B 1008 C 1008 D 2032                            <= unreasonable

    Curious indeed! The result from g++ is what I thought a better engineered
Cfront might do, however, I now think that g++ is wrong. ( see below )

In article <rae.656801817@fred> rae@gpu.utcs.toronto.edu (Reid Ellis) writes:
>The source being discussed is appended at the end of this article
>after a linefeed.  I noticed that the struct 'D' does not need to use
>"virtual" for deriving from B and C [although it shouldn't matter for
>this code] and tried removing them.
>
>Under Apple's MPW C++ compiler, I get the previously mentioned cfront
>output:
>A 1000 B 1008 C 1008 D 2032
>
>If, however, I change the line
>"struct D : virtual public B, virtual public C {"
>to
>"struct D : public B, public C {"
>then I get the more expected result of:
>A 1000 B 1008 C 1008 D 1020
>
>Hope this pins the problem down a bit more.  And if you need to, use
>this as a workaround.
>					Reid


   This, I believe is paramount to the question at hand. The declarations
 of B and C as virtual to D do affect how subsequent derivations are treated.


In article <7050032@hpcupt1.cup.hp.com> jamiller@hpcupt1.cup.hp.com (Jim Miller) writes:
>
>IMHO: the 2K size is a bug.
>
>
>E&S's ARM, 10.1: "
>
>      class V { /* ... */ };
>      class A : virtual public V { /* ... */ };
>      class B : virtual public V { /* ... */ };
>      class C : public A, public B { /* ... */ };
>
>   Here class C has only one sub-object of class V. "

    yes, but thats a different matter....

    Now to the nexus of the question. Given the aforementioned definitions
of A, B, C, and D, we derive 

class E : public virtual B { int evar; } ;

class F : public D, public E { int fvar ; } ;

    now F::E and F::D not only share a common A, but a common B as well.
 However, methods that act upon E do not know anything D or D's defintion of
 B. If, the definition of D is such that B and C are collapsed together as it
 appears that g++ is doing, then E's methods, with compiler generated offsets
 into B, are, in fact, going to munge C or D. Only Cfront's inclusion of a
 complete B into D is going to prevent this. This might not be appearent given
 the above examples, as they are simple enought that the translator/complier
 could guarantee proper ordering of members, but with arbitrarily complex
 definitions I dont believe this would always be true.
 
   Of course, I could be wrong and this could be a bug in the implementation
 of Cfront. If so, then I'm sure there is someone out there in netland willing
 to correct me. =-}


-- 
 sking@nowhere			      | C++ is to C, what espresso is to
..!cs.utexas.edu!ut-emx!nowhere!sking | coffee; Definitely an acquired taste,
				      | but once you have acquired that taste,
				      | you cant go back ...

bashford@scripps.edu (Don Bashford) (10/30/90)

In article <1990Oct29.054455.1989@nowhere.uucp> sking@nowhere.uucp (Steven King) writes:
>    now F::E and F::D not only share a common A, but a common B as well.
> However, methods that act upon E do not know anything D or D's defintion of
> B. If, the definition of D is such that B and C are collapsed together as it
> appears that g++ is doing, then E's methods, with compiler generated offsets
> into B, are, in fact, going to munge C or D. 

Aren't you confusing classes with *instances* of classes?

Don Bashford
bashford@scripps.edu