virtual method table for multiple-inheritance

后端 未结 2 926
花落未央
花落未央 2021-01-12 09:39

I\'m reading this article \"Virtual method table\"

Example in the above article:

class B1 {
public:
  void f0() {}
  virtual void f1() {}
  int int_i         


        
2条回答
  •  不思量自难忘°
    2021-01-12 09:45

    Given your class hierarchy, an object of type B2 will have the following memory footprint.

    +------------------------+
    | pointer for B2 vtable  |
    +------------------------+
    | int_in_b2              |
    +------------------------+
    

    An object of type D will have the following memory footprint.

    +------------------------+
    | pointer for B1 vtable  |
    +------------------------+
    | int_in_b1              |
    +------------------------+
    | pointer for B2 vtable  |
    +------------------------+
    | int_in_b2              |
    +------------------------+
    | int_in_d               |
    +------------------------+
    

    When you use:

    D* d  = new D();
    d->f2();
    

    That call is the same as:

    B2* b  = new D();
    b->f2();
    

    f2() can be called using a pointer of type B2 or pointer of type D. Given that the runtime must be able to correctly work with a pointer of type B2, it has to be able to correctly dispatch the call to D::f2() using the appropriate function pointer in B2's vtable. However, when the call is dispatched to D:f2() the original pointer of type B2 must somehow be offset properly so that in D::f2(), this points to a D, not a B2.

    Here's your example code, altered a little bit to print useful pointer values and member data to help understand the changes to the value of this in various functions.

    #include 
    
    struct B1 
    {
       void f0() {}
       virtual void f1() {}
       int int_in_b1;
    };
    
    struct B2 
    {
       B2() : int_in_b2(20) {}
       void test_f2()
       {
          std::cout << "In B::test_f2(), B*: " << (void*)this << std::endl;
          this->f2();
       }
    
       virtual void f2()
       {
          std::cout
             << "In B::f2(), B*: " << (void*)this
             << ", int_in_b2: " << int_in_b2 << std::endl;
       }
    
       int int_in_b2;
    };
    
    struct D : B1, B2 
    {
       D() : int_in_d(30) {}
       void d() {}
       void f2()
       {
          // ======================================================
          // If "this" is not adjusted properly to point to the D
          // object, accessing int_in_d will lead to undefined 
          // behavior.
          // ======================================================
    
          std::cout
             << "In D::f2(), D*: " << (void*)this
             << ", int_in_d: " << int_in_d << std::endl;
       }
       int int_in_d;
    };
    
    int main()
    {
       std::cout << "sizeof(void*) : " << sizeof(void*) << std::endl;
       std::cout << "sizeof(int)   : " << sizeof(int) << std::endl;
       std::cout << "sizeof(B1)    : " << sizeof(B1) << std::endl;
       std::cout << "sizeof(B2)    : " << sizeof(B2) << std::endl;
       std::cout << "sizeof(D)     : " << sizeof(D) << std::endl << std::endl;
    
       B2 *b2 = new B2();
       D  *d  = new D();
       b2->test_f2();
       d->test_f2();
       return 0;
    }
    

    Output of the program:

    sizeof(void*) : 8
    sizeof(int)   : 4
    sizeof(B1)    : 16
    sizeof(B2)    : 16
    sizeof(D)     : 32
    
    In B::test_f2(), B*: 0x1f50010
    In B::f2(), B*: 0x1f50010, int_in_b2: 20
    In B::test_f2(), B*: 0x1f50040
    In D::f2(), D*: 0x1f50030, int_in_d: 30
    

    When the actual object used to call test_f2() is D, the value of this changes from 0x1f50040 in test_f2() to 0x1f50030 in D::f2(). That matches with sizeof B1, B2, and D. The offset of B2 sub-object of a D object is 16 (0x10). The value of this in B::test_f2(), a B*, is changed by 0x10 before the call is dispatched to D::f2().

    I am going to guess that the value of the offset from D to B2 is stored in B2's vtable. Otherwise, there is no way a generic function dispatch mechanism can change the value of this properly before dispatching the call to the right virtual function.

提交回复
热议问题