[comp.lang.c++] Explanation desired

kanner@apple.UUCP (05/21/87)

I have been very puzzled by some of the C code produced by cfront when
declaring an object for a class that has virtual functions.  Here is
part of an example:

C++ source:

struct base{
	virtual void f1();
	virtual void f2();
	virtual void f3();
};

struct d1 : base {
	virtual void f1();
	virtual void f2();
};

struct d11 : d1 {
	virtual void f1();
	virtual void f2();
};

d11 od11;

...


Now the C code produced by "d1 od11;" follows; I have removed 17
levels of redundant parentheses, include 14 consecutive left-hand ones
at the start, and I have simplified the identifier _au1_od11 to od11
for readability:

(struct base *) (struct d1 *) &od11->_base__vptr = base__vtbl,
(struct base *) (struct d1 *) &od11,
(struct d1 *) &d11->_base__vptr = d1__vtbl,
(struct d1 *) &od11;
&od11->_base__vptr = d11__vtbl, // This line appears to be valid
&od11;

As far as I can tell, only the line I have commented appears to do
anything correct and useful.  Can anyone tell me what is going on?
-- 
Herb Kanner
Apple Computer, Inc.
{idi, ios, nsc}!apple!kanner

mikem@otc.UUCP (05/22/87)

In article <807@apple.UUCP>, kanner@apple.UUCP (Herbert Kanner) writes:
> I have been very puzzled by some of the C code produced by cfront when
> declaring an object for a class that has virtual functions.  Here is
> part of an example:
> 
> C++ source:
> 
> struct base{
> 	virtual void f1();
> 	virtual void f2();
> 	virtual void f3();
> };
> 
> struct d1 : base {
> 	virtual void f1();
> 	virtual void f2();
> };
> 
> struct d11 : d1 {
> 	virtual void f1();
> 	virtual void f2();
> };
> 
> d11 od11;
> 
> ...
> 
> Now the C code produced by "d1 od11;" follows; I have removed 17
> levels of redundant parentheses, include 14 consecutive left-hand ones
> at the start, and I have simplified the identifier _au1_od11 to od11
> for readability:
> 
> (struct base *) (struct d1 *) &od11->_base__vptr = base__vtbl,
> (struct base *) (struct d1 *) &od11,
> (struct d1 *) &d11->_base__vptr = d1__vtbl,
> (struct d1 *) &od11;
> &od11->_base__vptr = d11__vtbl, // This line appears to be valid
> &od11;

Firstly, you've mis-removed the parentheses. It should look more like:

    (
	(struct d1 *)(&od11)->_base__vptr = base__vtbl,
	/* ..... */

	 &od11
    );

The explanation of what happens in cases like this is as follows:

//----------------------------------------------------------------------
// C-Code associated with declaration of struct base:

struct base {
	int    (**_base__vptr)();
};

char    _base_f1();
char    _base_f2();
char    _base_f3();

static int    (*base__vtbl[])() = {
    				     (int (*)()) _base_f1,
				     (int (*)()) _base_f2,
				     (int (*)()) _base_f3,
				     0
				  };

	// The _base__vptr member of every instance of struct base is
	// automatically initialised by cfront to point to base__vtbl
	// as explained in detail below.
	//
	// base_vtbl is an array of pointers to the actual functions for
	// struct base. Each class derived from struct base has its own
	// vtbl, as can be seen below.
	// 
	// Anything derived from base will be contain a _base__vptr
	// as its first member. When the virtual member f1() is called for
	// a base* bp, say, // code resembling the following is generated:
	//
	//	(*(bp->_base__vptr[0]))();

//----------------------------------------------------------------------
// C-code associated with declaration of struct d1:

struct d1 {
	int    (**_base__vptr)();
};

char    _d1_f1();
char    _d1_f2();

static int    (*d1__vtbl[])() = {
				    (int (*)()) _d1_f1,
				    (int (*)()) _d1_f2,
				    (int (*)()) _base_f3,
				    0
				};

	// Thus if a d1* had been assigned to bp in the example above,
	// invocation of the virtual function will find the correct
	// function d1::f1
	//
	// Thus, for a derived class, the _base__vptr must be initialised
	// to point to the correct vtbl.
	//
	// Also note how d1__vtbl inherits _base_f3 as the 3rd member of
	// the vtbl.

//----------------------------------------------------------------------
// C-code associated with declaration of struct d11:

struct d11 {
	int    (**_base__vptr)();
};

char    _d11_f1();
char    _d11_f2();

static int    (*d11__vtbl[])() = {
				    (int (*)()) _d11_f1,
				    (int (*)()) _d11_f2,
				    (int (*)()) _base_f3,
				    0
				 };

//----------------------------------------------------------------------

struct d11 od11;

I've now removed some C code which is not relevant to the explanation.
Essentially, what has to happen is that the _base__vptr member for od11
has to be initialised to point to d11__vtbl. However, this is done by
effectively calling a "default" inline constructor for d11. (Any class
with virtual functions will exhibit this, even if there are no constructors
given, as is the case here.)

Removing casts for the sake of readability, the strange line of code would
not appear in the C output is really nothing more than:

    (
	(od11._base__vptr = _base__vtbl, &od11),
	(od11._base__vptr = _d1__vtbl, &od11),
	(od11._base__vptr = _d11__vtbl, &od11)
    );

What's happening here is that the "default" constructor for each of
the base classes is being called in the correct order. The reason for all
the &od11's is that this inline code is normally used in the context of:

    struct base  *bp;

    /*  And  "bp = new base;"  would generate something like: */

    bp = (bp = _new(sizeof(base)), bp->_base__vptr = _base_vtbl, bp);

I.e: The strange comma-separated expressions enable easy inline
initialisation of the result of the "new" operator. It could perhaps be
simplified, but only at the expense of complexity within cfront.

It might be argued that there's some unnecessary overhead here, since
the _base__vptr is assigned to three times. However, any decent
optimizer will throw the first two away.

So all that is really happening is simply:

	od11._base__vptr = _d11__vtbl;

I.e: The vptr for od11 is being initialised to point to struct d11's vtbl.

			Mike Mowbray
			Systems Development
			Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz