简体   繁体   中英

Understanding virtual table in multiple inheritance

I have a class implementing two abstract classes, like the following. No virtual inheritance. No data member.

class IFace1 {
public:
    virtual void fcn(int abc) = 0;
};

class IFace2 {
public:
    virtual void fcn1(int abc) = 0;
};

class RealClass: public IFace1, public IFace2 {
public:
    void fcn(int a) {
    }

    void fcn1(int a) {
   }
};

And I find the vtable and object memory layout for RealClass is like the following.

Vtable for RealClass
RealClass::_ZTV9RealClass: 7u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI9RealClass)
16    (int (*)(...))RealClass::fcn
24    (int (*)(...))RealClass::fcn1
32    (int (*)(...))-8
40    (int (*)(...))(& _ZTI9RealClass)
48    (int (*)(...))RealClass::_ZThn8_N9RealClass4fcn1Ei

Class RealClass
    size=16 align=8
    base size=16 base align=8
RealClass (0x2af836d010e0) 0
    vptr=((& RealClass::_ZTV9RealClass) + 16u)
    IFace1 (0x2af836cfa5a0) 0 nearly-empty
        primary-for RealClass (0x2af836d010e0)
    IFace2 (0x2af836cfa600) 8 nearly-empty
        vptr=((& RealClass::_ZTV9RealClass) + 48u)

I am confused about this. What is RealClass::_ZThn8_N9RealClass4fcn1Ei? Why the vptr of IFace2 points to that? What happens when I call fcn1 from IFace2 *? How does the program finds RealClass::fcn1 in the Vtable of RealClass? I guess it somehow need to use the IFace2 vptr, but not clear exactly how.

Warning: Most of the stuff below is of course implementation and platform dependent and simplified. I'll follow the way I see it is implemented in your examples -- probably GCC, 64-bits.


First, what is the contract for instances of virtual classes? Eg if you have a variable IFace1* obj :

  • There is a pointer to virtual table at obj+0 .
  • Any member data fields would continue at obj+8 ( sizeof(void*) ).
  • The virtual table contains one record which points to void fcn(int) at vtbl+0 .
  • In the table, there is also a pointer to typeinfo of the class at vtbl-8 (used by dynamic_cast etc.) and " offset to base " at vtbl-16 .

Any function which sees a variable of type IFace1* can depend on this being true. Similarly for IFace2* .

  • If they want to call the virtual function void fcn(int) , they look at obj+0 to get the vtable, then at vtbl+0 and call the address found there. this is set to obj .
  • If they want to access a member field (by themselves, eg if the field has public access, or if there is an inline accessor), they just read/write the member at its address obj+xxx .
  • If they want to see what type they really have, they subtract the value at vtbl-16 from the address to their object, then look at the typeinfo pointer of the vtable referenced by the base object.

Now, how can you compiler satisfy these requirements for a class with multiple inheritance?

1) First it needs to generate the structure for itself. The virtual table pointer must be at obj+0 , so there it is. How will the table look like? Well, the offset to base is 0, obviously, the typeinfo data and pointer to it is generated easily, then the first virtual function and the second, nothing special. Anyone who knows the definition of RealClass can do the same calculations, so they know where to find the functions in the vtable etc.

2) Then it goes to make it possible to let RealClass be passed around as IFace1 . So it needs to have a pointer to virtual table in the IFace1 format somewhere in the object, then the virtual table must have that one record for void fcn(int) .

The compiler is clever and sees that it can reuse the first virtual table it has generated, because it complies with these requirements. If there were any member fields, they would be stored after the first pointer to the virtual table, so even them could be accessed simply as if the derived class was the base one. So far so good.

3) Finally, what to do with the object so others will be able to use it as IFace2 ? The one vtable already created cannot be used anymore, because IFace2 needs its void fcn1(int) to be at vtbl+0 .

So another virtual table is created, the one you see immediately following the first one in your dump, and a pointer to it is stored in RealClass at the next available place. This second table needs to have offset to base set to -8, because the real object starts at offset -8. And it contains just the pointer to that IFace2 virtual function, void fcn1(int) .

The virtual pointer in the object (at offset obj+8 ) would be then followed by any member data fields of IFace2 , so that any inherited or inline functions could again work when given the pointer to this interface.


OK, now how can someone call the fcn1() from IFace2 ? What is that non-virtual thunk to RealClass::fcn1(int) ?

If you pass your RealClass* pointer to a stranger function which takes IFace2* , the compiler will emit code to increase your pointer by 8 (or however large sizeof(void*) + sizeof(IFace1) is), so that the function gets the pointer which starts with the virtual table pointer of IFace2 , then its member fields -- just as agreed in the contract I outlined earlier.

When that function wants to call void IFace2::fcn1(int) , it looks into the virtual table, goes to the record of this particular function (the first and only one) and calls it, with this set to the address being passed as pointer to IFace2 .

And here arises a problem: If someone invokes this method implemented in RealClass on a RealClass pointer, this points to the base of RealClass . The same with IFace1 . But if it is invoked by someone having a pointer to the IFace2 interface, this points 8 (or however many) bytes into the object instead!

So the compiler would need to generate the function multiple times to accomodate this, otherwise it could not access member fields and other methods correctly, as it differs depending on who is calling the method.

Instead of having the code really twice, the compiler optimizes this by creating that hidden implicit small thunk function instead, which just

  1. decreases the this pointer by the proper amount,
  2. calls the real method, which can now work fine regardless of who invoked it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM