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