C++ Virtual Table
Introduction
The C++ virtual table is the fundamental building block for C++ runtime polymorphisms. A virtual table (vtable) is a table of information used to dispatch virtual functions, to access virtual base class subobjects, and to access information for runtime type identification (RTTI).
In this blog post, I would like to discuss the C++ virtual table in detail and show a couple of concrete examples.
Non-Virtual Inheritance
Non-virtual inheritance is the most straightforward inheritance that we commonly use in C++. If the inheritance access specifiers, including private, public, and protected, do not have the keyword virtual, then it’s the non-virtual inheritance. The class that another class inherits is called the base class, and the other class is called the derived class.
Example
The following example created four classes Fruit, Drug, Apple, and Orange.
1 | Fruit |
1 | Fruit Drug |
The Apple class inherits from the Fruit class. The Orange class inherits from multiple classes, including the Fruit class and the Drug class.
1 | class Drug |
Class Hierarchy and Virtual Table
We could dump class hierarchy information, including the virtual table information, using -fdump-lang-class for the GCC compiler. The field Vtable, i.e., virtual table, is the most critical component for C++ runtime polymorphisms.
1 | # The hierarchy and virtual table information was saved in the file |
We will go through the class hierarchy information, including the virtual table information, for each class in detail.
1 | Vtable for Drug |
Drug::_ZTV4Drug: 6 entries says the name of the vtable, which is a static array, is Drug::_ZTV4Drug and there are 6 function pointer entries in the static array. The offset of each entry is 8 bytes.
0 (int (*)(...))0is the offset-to-top value for theDrugclass pointer adjustment. This is particularly used for dynamically switching the vtable pointers between different base classes and derived classes usingdynamic_cast. In this case, becauseDrugis just the base class and there is no need for pointer adjustment forDrugobjects, so the offset-to-top value is 0. As we will see later, the offset-to-top value in the vtable for theDrugsubobject in the classes derived fromDrugmight not be 0.8 (int (*)(...))(& _ZTI4Drug)is the typeinfo pointer to the type information of theDrugclass. This allows the user to know at runtime what exactly the object type is, even if the user just receives a base class pointer to a polymorphic class object. Without this field, given a base class pointer to a polymorphic class object, the user would not know whether base class pointer points to a base class object or derived class object. In this case, the typeinfo pointer points to a buffer where theDrugclass description is stored. As we will see later, the typeinfo pointer in the vtable for theDrugsubobject in the classes derived fromDrugwill point to the buffer that stores the derived class description instead of theDrugclass description.16 0and24 0are the pairs of entries for for virtual destructors. Why there are two destructors? The first destructor, called the complete object destructor, performs the destruction without callingdelete()on the object. The second destructor, called the deleting destructor, callsdelete()after destroying the object. Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor, performs destruction of the object but not its virtual base subobjects, and does not calldelete(). Sometimes, we would like to only destroy the object without deallocating the memory. That’s why we have two destructors. Because theDrugclass is an abstract class due to it has at least one pure virtual function, it cannot be constructed independently. Therefore, there is no need for destructors. That’s why the entries for virtual destructors are null pointers in this case.32 (int (*)(...))__cxa_pure_virtualand40 (int (*)(...))__cxa_pure_virtualare the pointers to the pure virtual functionsDrug::foo()andDrug::baz()respectively which will be overridden by the derived class function implementations.
1 | Class Drug |
This tells us that a Drug class object base size is 12 bytes, because sizeof(Drug::vtable) + sizeof(Drug::m_property) = 8 + 4 = 12. Because the alignment is 8 bytes, the actual size of a Drug class object is padded to 16 bytes. vptr=((& Drug::_ZTV4Drug) + 16) tells us that the virtual functions starts from the address ((& Drug::_ZTV4Drug) + 16) which is the 16 0 reserved for the destructors.
1 | Vtable for Fruit |
Understanding the Fruit class should be almost the same as understanding the Drug class. The small difference is that the Fruit::bar virtual function has already been defined in the base class Fruit and is therefore not a pure virtual function. The base size of the Fruit class object is sizeof(Fruit::vtable) + sizeof(Fruit::m_size) + sizeof(Fruit::m_id) + sizeof(Fruit::m_country) = 8 + 8 + 4 + 1 = 21 bytes. Because of the alignment requirement, the size of the Fruit class object is padded to 24 bytes.
Now let’s look at the Apple class which is derived from the Fruit class.
1 | Vtable for Apple |
Apple::_ZTV5Apple: 7 entries says the vtable name is Apple::_ZTV5Apple and there are 7 function pointer entries in the vtable. The offset of each entry is 8 bytes.
0 (int (*)(...))0is the offset-to-top value for theFruitandAppleclass pointer adjustment. Because theAppleclass only has one base class, besides the vtable for theFruitbase class, the vtable for theAppleclass is just extended from the vtable for theFruitbase class. Thus theAppleclass and theFruitbase class share the same vtable pointer, and there is no need for pointer adjustment.8 (int (*)(...))(& _ZTI5Apple)is the typeinfo pointer to the type information of theAppleclass.16 (int (*)(...))Apple::~Appleand24 (int (*)(...))Apple::~Appleare the function pointers to complete object destructor and the deleting destructor. When anAppleclass object is constructed, theFruitbase class constructor is first called followed by theAppleclass constructor is called. When anAppleclass object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, theAppledestructor is first called followed by theFruitdestructor is called. When anAppleclass object is destroyed via anAppleclass pointer or aFruitclass base pointer, the deleting destructor of theAppleclass, which is different from the complete object destructor, will be called via the vtable. Inside the deleting destructor, after the theAppleclass components are removed, theFruitbase class destructor will be called. Finally, theAppleclass object is deallocated from the memory.32 (int (*)(...))Apple::foois the pointer to the overriding functions forFruit::foo. So even if it is aFruitbase class pointer pointing to anAppleclass object, it can always find the overriding functionApple::foocorrectly via the vtable.40 (int (*)(...))Fruit::baris the function pointer to the functionFruit::bardefined in theFruitbase class since it has never been overridden.48 (int (*)(...))Apple::apple_foois the pointer to the new virtual functionApple::apple_foofor theAppleclass. It belongs to theAppleclass vtable and is only accessible via anAppleclass pointer, even though theAppleclass vtable is just extended from theFruitclass vtable on the memory.
1 | Class Apple |
The base size of the Apple class object is sizeof(Fruit) + sizeof(Apple::m_color) = 24 + 4 = 28 bytes. Because of the alignment requirement, the size of the Apple class object is padded to 32 bytes. The only vtable pointer vptr for both the Apple and Fruit class points to the first destructor function of Apple, 16 (int (*)(...))Apple::~Apple.
1 | Vtable for Orange |
Drug::_ZTV6Orange: 14 entries says the vtable name is Drug::_ZTV6Oranges and there are 14 function pointer entries in the vtable. The offset of each entry is 8 bytes. Because the Orange class is derived from both the Fruit class and the Drug class. This vtable can serve three different class pointers, including the pointers to the Fruit, Drug, and Orange classes.
0 (int (*)(...))0is the offset-to-top value for theFruitandOrangeclass pointer adjustment. For the primary base classFruitand theOrangeclass, because their pointers both points to the beginning of theOrangeclass object, there is no need for pointer adjustment. Therefore, the offset-to-top value for theFruitand theOrangeclass is 0.8 (int (*)(...))(& _ZTI6Orange)is the typeinfo pointer to the type information of theOrangeclass.16 (int (*)(...))Orange::~Orangeand24 (int (*)(...))Orange::~Orangeare the function pointers to complete object destructor and the deleting destructor. When anOrangeclass object is constructed, theFruitbase class constructor is first called, followed by theDrugbase class constructor is called, followed by theOrangeclass constructor is called. When anOrangeclass object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, theOrangedestructor is first called, followed by theDrugdestructor is called, followed by theFruitdestructor is called. When anOrangeclass object is destroyed via anOrangeclass pointer or aFruitclass base pointer, The deleting destructor of theOrangeclass will be called via the vtable. Before the deleting destructor gets returned, theDrugandFruitbase class destructors are called and finally theOrangeclass object is deallocated from the memory.32 (int (*)(...))Orange::foois the pointer to the overriding functions forFruit::foo. So even if it is aFruitbase class pointer pointing to anOrangeclass object, it can always find the overriding functionOrange::foocorrectly via the vtable. Note that even though theDrugbase class also has a virtual functionDrug::foothat’s also overridden byOrange::foo, thisOrange::foodoes not belong to theDrugbase class vtable and is not accessible via aDrugbase class pointer.40 (int (*)(...))Orange::baris the pointer to the overriding functions forFruit::bar. So even if it is aFruitbase class pointer pointing to anOrangeclass object, it can always find the overriding functionOrange::barcorrectly via the vtable.48 (int (*)(...))Orange::bazis the pointer to the overriding functions forDrug::baz. ThisOrange::bazonly belongs to theOrangeclass vtable and it only accessible via aOrangeclass pointer.56 (int (*)(...))Orange::orange_baris the pointer to the new virtual functionOrange::orange_barfor theOrangeclass. It belongs to theOrangeclass vtable and is only accessible via anOrangeclass pointer, even though theOrangeclass vtable is just extended from theFruitclass vtable on the memory.64 (int (*)(...))-24is the offset-to-top value for theDrugclass pointer adjustment. Suppose the address of aDrugpointer pointing to aOrangeclass object isx, then the address to the actualOrangeclass object isx - 24. Suppose the address of aDrugpointer pointing to aDrugclass object isx, as we have seen in theDrugclass vtable, the offset-to-top value is0instead of-24. So there is no point adjustment if the actual object is aDrugclass object. Using this mechanism, C++ can dynamically change the polymorphic class correctly from the base class pointers during runtime.72 (int (*)(...))(& _ZTI6Orange)is the typeinfo pointer to the type information of theOrangeclass.80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Evand88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Evare the function pointers to complete object destructor and the deleting destructor. They are almost the same as the16 (int (*)(...))Orange::~Orangeand24 (int (*)(...))Orange::~Orange. The difference is that they belong to theDrugclass vtable and can only be accessed by theDrugbase class pointer. The implementation of80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Evand88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Evmight involve thethispointer adjustment so that16 (int (*)(...))Orange::~Orangeand24 (int (*)(...))Orange::~Orangeis actually called for destruction. But it can just be compiler dependent.96 (int (*)(...))Orange::_ZThn24_N6Orange3fooEvis the the pointer to the overriding functions forDrug::foo. So even if it is aDrugbase class pointer pointing to anOrangeclass object, it can always find the overriding functionOrange::foocorrectly via the vtable. It belongs to theDrugclass vtable and can only be accessed by theDrugbase class pointer.104 (int (*)(...))Orange::_ZThn24_N6Orange3bazEvis the the pointer to the overriding functions forDrug::baz. So even if it is aDrugbase class pointer pointing to anOrangeclass object, it can always find the overriding functionOrange::bazcorrectly via the vtable. It belongs to theDrugclass vtable and can only be accessed by theDrugbase class pointer.
1 | Class Orange |
The base size of the Orange class object is sizeof(Fruit) + sizeof(Drug) + sizeof(Orange::m_weight) = 24 + 16 + 8 = 48 bytes, and it happens to meet the alignment requirement. The Drug base class component offset to the beginning of the Orange class is 24 bytes, which means it’s arranged after the Fruit class components on the stack memory. It makes sense because the Fruit class is the primary base class for the Orange class.
The vtable pointer vptr for the Orange class and the Fruit class are shared and it points to the first destructor function of Orange, 16 (int (*)(...))Orange::~Orange. There is another vtable pointer vptr for the Drug class and it points to the first destructor function of Orange, 80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev.
Class Memory Layout
Using pahole, we could also verify the class object memory layouts and the vtable entries.
1 | $ pahole inheritance |
Virtual Inheritance
Unlike the non-virtual inheritance described above, virtual inheritance uses the keyword virtual for the access specifiers, including private, public, and protected. The class that a class inherits with a virtual keyword is called the virtual base class.
The difference between the conventional non-virtual inheritance and the virtual inheritance is that virtual inheritance allows diamond hierarchy and only one instance of the virtual base class will be created. In the following example, when class A is the virtual base class for both class B and C, and an object of class D, which derives from both class B and C, is created, only one subobject of class A will be created.
1 | A |
If class A is not a virtual base class for both class B and C, two subobjects of class A will be created. In some scenarios, this is undesired or unnecessary.
1 | A A |
Example
The following example created five classes Item, Fruit, Drug, Apple, and Orange.
1 | Item |
1 | Item |
The Item class is a virtual base for both the Fruit and Drug class. The Apple class inherits from the Fruit class. The Orange class inherits from multiple classes, including the Fruit class and the Drug class. This time, because the Item class is virtually inherited by both the Fruit and Drug class, only one instance of the Item subobject will be created for the Orange object.
1 | class Item |
Class Hierarchy, Virtual Table, and Virtual Table Table
In the same way, we could dump class hierarchy information, including the virtual table information, using -fdump-lang-class for the GCC compiler. However, suddenly, the information becomes very complicated because of the virtual inheritance. There is also an addition field VTT, i.e., virtual table table or vtable table, which includes the transient vtables used for object construction and destruction. We will discuss virtual table table in my article “C++ Virtual Table Table”.
1 | $ g++ -g -fdump-lang-class virtual_inheritance.cpp -o virtual_inheritance |
We will go through the class hierarchy information, including the virtual table information, for each class in detail.
1 | Vtable for Item |
Understanding the Item class is straightforward given we what have discussed in the previous sections.
1 | Vtable for Drug |
Drug::_ZTV4Drug: 17 entries says the vtable name is Drug::_ZTV4Drug and there are 17 function pointer entries in the vtable. The offset of each entry is 8 bytes. Because the Drug class virtually inherits the Item class, the vtable looks more complicated than the previous ones we have seen. The major complications come from the additional entries.
0 16is the virtual-base-offset or vbase-offset which we have not seen in the vtable without virtual inheritance. Given the derived classDrugpointer, by adding the vbase-offset to theDrugclass pointer, we could get the virtual base class pointer. Notice because the classItemis a virtual base for theDrugclass, unlike the non-virtual inheritance, it’s not constructed at the offset 0 to theDrugclass.64 0,72 18446744073709551600, and80 18446744073709551600are the virtual-call-offsets or vcall-offsets for the virtual baseItemclass pointer. It’s used for adjusting thethispointer used in the virtual functions from the virtual base class. Because there are three virtual functions in the virtual baseItemclass, includingItem::~Item,Item::qux, andItem::quux, we have three virtual-call-offsets for them. Although I am not exactly sure about which virtual-call-offset is used for which virtual function, my best educative guess is, the last virtual-call-offset80 18446744073709551600is used for the virtual destructorItem::~Item, the last but one virtual-call-offset72 18446744073709551600is used forItem::qux, and the first virtual-call-offset64 0is used forItem::quux. This educative guess seems to be consistent for other classes as well. For example, theItem::quxvirtual function has been overridden by theDrug::quxfunction, whereas theItem::quuxvirtual function has not. When theDrug::quxfunction is invoked via the virtual baseItemclass pointer through the120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEventry in the vtable, a derived classDrugobject should be passed to the function. But thethispointer actually points to the virtual baseItemclass object. To get the pointer pointing to theDrugobject,72 18446744073709551600is added to thethispointer. The18446744073709551600value is extremely confusing. But it’s just the two’s complement representation of the signed integer-16. Let’s check more specifically. The binary representation of18446744073709551600is111111111111111111111111111111111111111111111111111111111110000. The MSB is1, indicating a negative number. Inverting all the bits results in000000000000000000000000000000000000000000000000000000000001111. Adding1to the inverted binary number gives000000000000000000000000000000000000000000000000000000000010000. The resulting binary number,000000000000000000000000000000000000000000000000000000000010000, is16. The negated decimal value is 16. Then the original value18446744073709551600represents the decimal value-16. Now it makes sense, becausethis + (-16)will just point to theDrugobject. Similarly, for the virtual destructor, since it has been overridden by theDrugclass, the same vcall-offset has to be used for adjusting thethispointer used for the destructor so that it points to theDrugobject. Finally, where is the vcall-offset64 0is used? Note that theItem::quuxhas not been overridden by theDrugclass, anItemobject should be used for theItem::quuxfunction. Sincethisalready points to theItemvirtual base object, the offset is0.
In addition, notice that even though the Item virtual base class has a virtual function Item::quux, unlike the vtable for the non-virtual inheritance example, it’s not in the entry after 56 (int (*)(...))Drug::qux. But a Drug class pointer still have the access to call Item::quux. To do this, the Drug class pointer would be first adjusted to the virtual base Item class pointer using the vbase-offset value, the Item::quux function can be invoked by calling the quux method from the Drug class pointer.
1 | Class Drug |
The base size of the Drug class object is sizeof(Drug::vtable) + sizeof(Drug::m_property) = 8 + 4 = 12 bytes and it is padded to 16 bytes because the alignment requirement is 8 bytes. Notice that because the Item class is a virtual base class, it is not counted in the base size. The Item class object is 16 bytes. Finally, the size of the Drug class object is sizeof(Drug) + sizeof(Item) = 16 + 16 = 32 bytes. There are two vtable pointers. The Drug class vtable pointer points to the 24 0 entry in the vtable, whereas the Item class vtable pointer points to the 104 0 entry in the vtable.
Let’s finally take a look at the Orange class. We could use Orange, Fruit, Drug, and Item class pointers pointing to an Orange class object.
1 | Vtable for Orange |
0 48is the vbase-offset for theOrangeandFruitclass pointers.80 24is the vbase-offset for theDrugclass pointers.144 18446744073709551568,152 18446744073709551592, and160 18446744073709551568, which translate to144 -48,152 -24, and160 -48are the vcall-offsets for theItem::quux,Item::qux, andItem::~Itemoverriding functions. BecauseItem::quuxis overridden by theFruit::quux, the vcall-offset-48will be used for adjusting thethispointer from pointing to anItemobject to pointing to aFruitobject. This is the same for theItem::~Itemwhich is overridden by theOrange::~Orange. BecauseItem::quxis overridden by theDrug::qux, the vcall-offset-24will be used for adjusting thethispointer from pointing to anItemobject to pointing to aDrugobject. These offsets are also consistent with the offset-to-top values for theDrugandItemclass pointers in the vtable.
In addition, if there is a Drug class pointer points to an Orange class object, calling quux function via the Drug class pointer results in calling the Fruit::quux method. This is because the Drug class pointer would be first adjusted to the virtual base Item class pointer. Because the Item::quux virtual function has been overridden by the Fruit::quux function, 208 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv will be invoked instead.
1 | Class Orange |
The Orange class object base size, size, vtable pointers, and memory layout is self-explanatory and is consistent with our understanding of the vtable so far.
Class Memory Layout
Using pahole, we could also verify the class object memory layouts and the vtable entries.
1 | $ pahole virtual_inheritance |
FAQs
Why There Are Two Destructors In the Virtual Table?
There are actually three types destructors.
- Base object destructor of a class
T
A function that runs the destructors for non-static data members ofTand non-virtual direct base classes ofT.
- Complete object destructor of a class
T
A function that, in addition to the actions required of a base object destructor, runs the destructors for the virtual base classes ofT.
- Deleting destructor of a class
T
A function that, in addition to the actions required of a complete object destructor, calls the appropriate deallocation function (i.e,. operator delete) forT.
The complete object destructor and deleting destructor are generated by the compiler and are used via the vtable.
Sometimes, we would like to only destroy the object without deallocating the memory or deallocating the memory separately. The complete object destructor and deleting destructor become useful in this situation. Please see my previous article “C++ Dynamic Memory Management” for more information.
Why Constructors Are Not In the Virtual Table?
There are no virtual constructors. Therefore, they are not in the vtable.
Conclusions
We have seen what vtable is and how vtable is critically used for C++ runtime polymorphisms. However, we would not discuss why vtable is designed in such a way and whether it is optimal or not here, since it is too complicated.
References
C++ Virtual Table