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 (*)(...))0
is the offset-to-top value for theDrug
class pointer adjustment. This is particularly used for dynamically switching the vtable pointers between different base classes and derived classes usingdynamic_cast
. In this case, becauseDrug
is just the base class and there is no need for pointer adjustment forDrug
objects, so the offset-to-top value is 0. As we will see later, the offset-to-top value in the vtable for theDrug
subobject in the classes derived fromDrug
might not be 0.8 (int (*)(...))(& _ZTI4Drug)
is the typeinfo pointer to the type information of theDrug
class. 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 theDrug
class description is stored. As we will see later, the typeinfo pointer in the vtable for theDrug
subobject in the classes derived fromDrug
will point to the buffer that stores the derived class description instead of theDrug
class description.16 0
and24 0
are 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 theDrug
class 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_virtual
and40 (int (*)(...))__cxa_pure_virtual
are 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 (*)(...))0
is the offset-to-top value for theFruit
andApple
class pointer adjustment. Because theApple
class only has one base class, besides the vtable for theFruit
base class, the vtable for theApple
class is just extended from the vtable for theFruit
base class. Thus theApple
class and theFruit
base 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 theApple
class.16 (int (*)(...))Apple::~Apple
and24 (int (*)(...))Apple::~Apple
are the function pointers to complete object destructor and the deleting destructor. When anApple
class object is constructed, theFruit
base class constructor is first called followed by theApple
class constructor is called. When anApple
class object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, theApple
destructor is first called followed by theFruit
destructor is called. When anApple
class object is destroyed via anApple
class pointer or aFruit
class base pointer, the deleting destructor of theApple
class, which is different from the complete object destructor, will be called via the vtable. Inside the deleting destructor, after the theApple
class components are removed, theFruit
base class destructor will be called. Finally, theApple
class object is deallocated from the memory.32 (int (*)(...))Apple::foo
is the pointer to the overriding functions forFruit::foo
. So even if it is aFruit
base class pointer pointing to anApple
class object, it can always find the overriding functionApple::foo
correctly via the vtable.40 (int (*)(...))Fruit::bar
is the function pointer to the functionFruit::bar
defined in theFruit
base class since it has never been overridden.48 (int (*)(...))Apple::apple_foo
is the pointer to the new virtual functionApple::apple_foo
for theApple
class. It belongs to theApple
class vtable and is only accessible via anApple
class pointer, even though theApple
class vtable is just extended from theFruit
class 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 (*)(...))0
is the offset-to-top value for theFruit
andOrange
class pointer adjustment. For the primary base classFruit
and theOrange
class, because their pointers both points to the beginning of theOrange
class object, there is no need for pointer adjustment. Therefore, the offset-to-top value for theFruit
and theOrange
class is 0.8 (int (*)(...))(& _ZTI6Orange)
is the typeinfo pointer to the type information of theOrange
class.16 (int (*)(...))Orange::~Orange
and24 (int (*)(...))Orange::~Orange
are the function pointers to complete object destructor and the deleting destructor. When anOrange
class object is constructed, theFruit
base class constructor is first called, followed by theDrug
base class constructor is called, followed by theOrange
class constructor is called. When anOrange
class object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, theOrange
destructor is first called, followed by theDrug
destructor is called, followed by theFruit
destructor is called. When anOrange
class object is destroyed via anOrange
class pointer or aFruit
class base pointer, The deleting destructor of theOrange
class will be called via the vtable. Before the deleting destructor gets returned, theDrug
andFruit
base class destructors are called and finally theOrange
class object is deallocated from the memory.32 (int (*)(...))Orange::foo
is the pointer to the overriding functions forFruit::foo
. So even if it is aFruit
base class pointer pointing to anOrange
class object, it can always find the overriding functionOrange::foo
correctly via the vtable. Note that even though theDrug
base class also has a virtual functionDrug::foo
that’s also overridden byOrange::foo
, thisOrange::foo
does not belong to theDrug
base class vtable and is not accessible via aDrug
base class pointer.40 (int (*)(...))Orange::bar
is the pointer to the overriding functions forFruit::bar
. So even if it is aFruit
base class pointer pointing to anOrange
class object, it can always find the overriding functionOrange::bar
correctly via the vtable.48 (int (*)(...))Orange::baz
is the pointer to the overriding functions forDrug::baz
. ThisOrange::baz
only belongs to theOrange
class vtable and it only accessible via aOrange
class pointer.56 (int (*)(...))Orange::orange_bar
is the pointer to the new virtual functionOrange::orange_bar
for theOrange
class. It belongs to theOrange
class vtable and is only accessible via anOrange
class pointer, even though theOrange
class vtable is just extended from theFruit
class vtable on the memory.64 (int (*)(...))-24
is the offset-to-top value for theDrug
class pointer adjustment. Suppose the address of aDrug
pointer pointing to aOrange
class object isx
, then the address to the actualOrange
class object isx - 24
. Suppose the address of aDrug
pointer pointing to aDrug
class object isx
, as we have seen in theDrug
class vtable, the offset-to-top value is0
instead of-24
. So there is no point adjustment if the actual object is aDrug
class 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 theOrange
class.80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
and88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
are the function pointers to complete object destructor and the deleting destructor. They are almost the same as the16 (int (*)(...))Orange::~Orange
and24 (int (*)(...))Orange::~Orange
. The difference is that they belong to theDrug
class vtable and can only be accessed by theDrug
base class pointer. The implementation of80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
and88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
might involve thethis
pointer adjustment so that16 (int (*)(...))Orange::~Orange
and24 (int (*)(...))Orange::~Orange
is actually called for destruction. But it can just be compiler dependent.96 (int (*)(...))Orange::_ZThn24_N6Orange3fooEv
is the the pointer to the overriding functions forDrug::foo
. So even if it is aDrug
base class pointer pointing to anOrange
class object, it can always find the overriding functionOrange::foo
correctly via the vtable. It belongs to theDrug
class vtable and can only be accessed by theDrug
base class pointer.104 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv
is the the pointer to the overriding functions forDrug::baz
. So even if it is aDrug
base class pointer pointing to anOrange
class object, it can always find the overriding functionOrange::baz
correctly via the vtable. It belongs to theDrug
class vtable and can only be accessed by theDrug
base 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 16
is the virtual-base-offset or vbase-offset which we have not seen in the vtable without virtual inheritance. Given the derived classDrug
pointer, by adding the vbase-offset to theDrug
class pointer, we could get the virtual base class pointer. Notice because the classItem
is a virtual base for theDrug
class, unlike the non-virtual inheritance, it’s not constructed at the offset 0 to theDrug
class.64 0
,72 18446744073709551600
, and80 18446744073709551600
are the virtual-call-offsets or vcall-offsets for the virtual baseItem
class pointer. It’s used for adjusting thethis
pointer used in the virtual functions from the virtual base class. Because there are three virtual functions in the virtual baseItem
class, 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 18446744073709551600
is used for the virtual destructorItem::~Item
, the last but one virtual-call-offset72 18446744073709551600
is used forItem::qux
, and the first virtual-call-offset64 0
is used forItem::quux
. This educative guess seems to be consistent for other classes as well. For example, theItem::qux
virtual function has been overridden by theDrug::qux
function, whereas theItem::quux
virtual function has not. When theDrug::qux
function is invoked via the virtual baseItem
class pointer through the120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
entry in the vtable, a derived classDrug
object should be passed to the function. But thethis
pointer actually points to the virtual baseItem
class object. To get the pointer pointing to theDrug
object,72 18446744073709551600
is added to thethis
pointer. The18446744073709551600
value 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 of18446744073709551600
is111111111111111111111111111111111111111111111111111111111110000
. The MSB is1
, indicating a negative number. Inverting all the bits results in000000000000000000000000000000000000000000000000000000000001111
. Adding1
to the inverted binary number gives000000000000000000000000000000000000000000000000000000000010000
. The resulting binary number,000000000000000000000000000000000000000000000000000000000010000
, is16
. The negated decimal value is 16. Then the original value18446744073709551600
represents the decimal value-16
. Now it makes sense, becausethis + (-16)
will just point to theDrug
object. Similarly, for the virtual destructor, since it has been overridden by theDrug
class, the same vcall-offset has to be used for adjusting thethis
pointer used for the destructor so that it points to theDrug
object. Finally, where is the vcall-offset64 0
is used? Note that theItem::quux
has not been overridden by theDrug
class, anItem
object should be used for theItem::quux
function. Sincethis
already points to theItem
virtual 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 48
is the vbase-offset for theOrange
andFruit
class pointers.80 24
is the vbase-offset for theDrug
class pointers.144 18446744073709551568
,152 18446744073709551592
, and160 18446744073709551568
, which translate to144 -48
,152 -24
, and160 -48
are the vcall-offsets for theItem::quux
,Item::qux
, andItem::~Item
overriding functions. BecauseItem::quux
is overridden by theFruit::quux
, the vcall-offset-48
will be used for adjusting thethis
pointer from pointing to anItem
object to pointing to aFruit
object. This is the same for theItem::~Item
which is overridden by theOrange::~Orange
. BecauseItem::qux
is overridden by theDrug::qux
, the vcall-offset-24
will be used for adjusting thethis
pointer from pointing to anItem
object to pointing to aDrug
object. These offsets are also consistent with the offset-to-top values for theDrug
andItem
class 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 ofT
and 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