简体   繁体   中英

Construction of the virtual table by compiler

When a derived class inherited the properties of n Base classes each with one virtual function, while constructing the virtual table for the derived class, why the compiler constructs an individual virtual table for each Base class? Why the compiler is not constructing a single virtual table consisting of all references for virtual functions of Base class and its own?

For example: In following program, the derived class inherited 3 Base classes each with one virtual function.

             class Base1
             {
                  public:
                     virtual void base1_func1(){cout<<"In Base1:base1_func1:"<<endl;} 
             };

             class Base2
             {
                  public:
                  virtual void base2_func1(){cout<<"In Base2:base2_func1:"<<endl;} 
             };

             class Base3
             {
                 public:
                 virtual void base3_func1(){cout<<"In Base3:base3_func1:"<<endl;} 
             };

            class Derived:public Base1, public Base2, public Base3
            {
            };

            typedef void (*Func) (void);

            int main()
            {
                Base1 b1;
                Base2 b2;
                Base3 b3;
                Derived d;
                Func f= NULL;

                cout<<"Size of Base1 obj:"<<sizeof(b1)<<endl;
                cout<<"Size of Base2 obj:"<<sizeof(b2)<<endl;
                cout<<"Size of Base3 obj:"<<sizeof(b3)<<endl;
                cout<<"Size of Derived obj:"<<sizeof(d)<<endl;
                cout<<"Printing the VPTR Address of Base1 obj b1 :"<< *((int *)(&b1)+0)<<endl;
                cout<<"Printing the Address of Base1 func1 in VTABLE:"<< (int *)*((int            *)*((int *)(&b1)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b1)+0)+0);
               f();
               cout<<"Printing the VPTR Address of Base2 obj b2 :"<< *((int *)(&b2)+0)<<endl;
               cout<<"Printing the Address of Base2 func1 in VTABLE:"<< (int *)*((int *)*((int *)(&b2)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b2)+0)+0);
               f();
               cout<<"Printing the VPTR Address of Base3 obj b3 :"<< *((int *)(&b3)+0)<<endl;
               cout<<"Printing the Address of Base3 func1 in VTABLE:"<< (int *)*((int *)*((int *)(&b3)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b3)+0)+0);
               f();

               cout<<"Printing the VPTR1 Address of Derived obj d :"<< *((int *)(&d)+0)<<endl;
               cout<<"Printing the VPTR2 Address of Derived obj d :"<< *((int *)(&d)+1)<<endl;
               cout<<"Printing the VPTR3 Address of Derived obj d :"<< *((int *)(&d)+2)<<endl;
               cout<<"Printing the Address of Derived base1_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&d)+0)+0);
               f();
              cout<<"Printing the Address of Derived base2_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+1)+0)<<endl;
              f = (Func)*((int *)*((int *)(&d)+1)+0);
              f();
              cout<<"Printing the Address of Derived base3_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+2)+0)<<endl;
             f = (Func)*((int *)*((int *)(&d)+2)+0);
             f();

            return 0;
         }

         Output:
         Size of Base1 obj:4
         Size of Base2 obj:4
         Size of Base3 obj:4
         Size of Derived obj:12
         Printing the VPTR Address of Base1 obj b1 :134517392
         Printing the Address of Base1 func1 in VTABLE:0x8048dfe
         In Base1:base1_func1:
         Printing the VPTR Address of Base2 obj b2 :134517424
         Printing the Address of Base2 func1 in VTABLE:0x8048e2a
         In Base2:base2_func1:
         Printing the VPTR Address of Base3 obj b3 :134517456
         Printing the Address of Base3 func1 in VTABLE:0x8048e56
         In Base3:base3_func1:
         Printing the VPTR1 Address of Derived obj d :134517512
        Printing the VPTR2 Address of Derived obj d :134517524
        Printing the VPTR3 Address of Derived obj d :134517536
        Printing the Address of Derived base1_func1 in VTABLE:0x8048dfe
        In Base1:base1_func1:
        Printing the Address of Derived base2_func1 in VTABLE:0x8048e2a
        In Base2:base2_func1:
        Printing the Address of Derived base3_func1 in VTABLE:0x8048e56
        In Base3:base3_func1:

The output clearly says that complier constructs an individual virtual table for each Base class inherited in derived class.

I may missing something, but doesn't:

cout<<"Printing the VPTR1 Address of Derived obj d :"<< *((int *)(&d)+0)<<endl;
cout<<"Printing the VPTR2 Address of Derived obj d :"<< *((int *)(&d)+1)<<endl;
cout<<"Printing the VPTR3 Address of Derived obj d :"<< *((int *)(&d)+2)<<endl;

You are just printing the address of the "X" d element. &d = the address of d (&d + X) = continue to the X element, or in other words + (X * sizeof(d)) (int *)(&d + X) = look at this address as a pointer to int (instead a pointer to d) *((int *)(&d + 2) = get the int value (basically the value of the address).

What I'm saying is, if you add to Derived class more private members and by that increase the sizeof(d) you will get a different values, but it's clear that the VPTR didn't move.

==edit==

Not sure how to do it, but you need to find the correct way to find the VPTR address

Note: The answer to this Question is purely implementation specific, the C++ standard does not even mention virtual table or virtual pointers so compilers are free to implement the dynamic dispatch with any mechanism, the standard only defines the behavior expected and as long as the compilers satisfy those requirements they are free to implement dynamic dispatch using any mechanism they choose.
Having said the above all known compilers implement the virtual mechanism using a vtable and a vptr mechanism.


The vtable is a table which stores addresses of every virtual method in that class or the classes from which it derives.
The vptr points to the vtable of that class.
The vptr is buried somewhere in the this pointer.

Whenever a derived class overiddes an method the compiler replaces the address of that particular function in the vtable with address of the overidden method. If there is no overidding then vtable keeps holding the address of the Base class method.

For eg:
Consider the following class hierarchy:

class shape
{
  public:
  virtual void Draw1();
  virtual void Draw2();
  virtual void Draw3();
};

The vtable for this class looks like:

基类V表

Consider a Derived class:

class line: public shape
{
  public:
  virtual void Draw1();
  virtual void Draw2();
};

The vtable for this class will be:

派生类vtable

At run-time first the vptr is fetched from this and then address of the function to be called is fetched from the relevant slot in the vtable , and then the method is called. Since each class has its own table with address of the overidden methods the appropriate overidden method can then be called.

Thus for dynamic dispatch to work the compiler needs to create separate vtable for each class.

Good read:
What happens in the hardware when I call a virtual function? How many layers of indirection are there? How much overhead is there?

If you override at least one function, the pointer in the virtual table needs to change and you need a completely new virtual table.

Even if you don't override anything, there may be RTTI-specific information "attached" to the virtual table (eg on a negative offset, or as a pointer in the table itself). RTTI is specific for type, so a virtual table has to be too. Also, the "single big virtual table" would not work in many multiple-inheritance scenarios.

The answer comes from two premises:

  • the virtual table contains both the virtual member pointers and extra type information needed for RTTI.
  • the virtual table of a base and all it's derived types must have the same memory layout for dynamic dispatch to work (when calling through a base pointer, the memory will be interpreted as a base vtable)

The combination of the conditions implies that you cannot extend a vtable by adding just new function pointers for different bases, as when you obtain a pointer to each base it will expect that it's vptr refers to a compatible vtable and that includes the type information. Note that at least theoretically you can extend the same vtable for deriving classes by just adding function pointers, as the derived type can reuse the RTTI pointer from the first base.

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