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
2
3
Fruit
|
Apple
1
2
3
Fruit   Drug
\ /
Orange

The Apple class inherits from the Fruit class. The Orange class inherits from multiple classes, including the Fruit class and the Drug class.

inheritance.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Drug
{
public:
Drug(int property) : m_property{property} {};
virtual ~Drug() = default;
virtual void foo() = 0;
virtual void baz() = 0;
int m_property;
};

class Fruit
{
public:
Fruit(double size, int id, char country)
: m_size{size}, m_id{id}, m_country{country} {};
virtual ~Fruit() = default;
virtual void foo() = 0;
virtual void bar(){};
double m_size;
int m_id;
char m_country;
};

class Apple : public Fruit
{
public:
Apple(double size, int id, char country, int color)
: Fruit{size, id, country}, m_color{color} {};
~Apple() = default;
void foo() override {}
void get_color() const {}
virtual void apple_foo() {}
int m_color;
};

class Orange : public Fruit, public Drug
{
public:
Orange(double size, int id, char country, double weight)
: Fruit{size, id, country}, Drug{4}, m_weight{weight} {};
~Orange() = default;
// Overriding Fruit virtual functions.
void foo() override {}
void bar() override {}
// Overriding Drug virtual functions.
void baz() override {}
// Orange non-virtual functions.
void get_weight() const {}
// Orange virtual functions.
virtual void orange_bar() {}
double m_weight;
};

int main()
{
double const apple_size{1.0};
int const apple_id{1};
char const apple_country{'c'};
int const apple_color{2};
double const orange_size{0.8};
int const orange_id{2};
char const orange_country{'n'};
double const orange_weight{0.6};
Apple apple{apple_size, apple_id, apple_country, apple_color};
Orange orange{orange_size, orange_id, orange_country, orange_weight};
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# The hierarchy and virtual table information was saved in the file
# inheritance.cpp.001l.class.
$ g++ -g -fdump-lang-class inheritance.cpp -o inheritance
$ cat inheritance.cpp.001l.class
Vtable for Drug
Drug::_ZTV4Drug: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Drug)
16 0
24 0
32 (int (*)(...))__cxa_pure_virtual
40 (int (*)(...))__cxa_pure_virtual

Class Drug
size=16 align=8
base size=12 base align=8
Drug (0x0x7f79a8d6d420) 0
vptr=((& Drug::_ZTV4Drug) + 16)

Vtable for Fruit
Fruit::_ZTV5Fruit: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Fruit)
16 0
24 0
32 (int (*)(...))__cxa_pure_virtual
40 (int (*)(...))Fruit::bar

Class Fruit
size=24 align=8
base size=21 base align=8
Fruit (0x0x7f79a8d6d6c0) 0
vptr=((& Fruit::_ZTV5Fruit) + 16)

Vtable for Apple
Apple::_ZTV5Apple: 7 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Apple)
16 (int (*)(...))Apple::~Apple
24 (int (*)(...))Apple::~Apple
32 (int (*)(...))Apple::foo
40 (int (*)(...))Fruit::bar
48 (int (*)(...))Apple::apple_foo

Class Apple
size=32 align=8
base size=28 base align=8
Apple (0x0x7f79a8c0e340) 0
vptr=((& Apple::_ZTV5Apple) + 16)
Fruit (0x0x7f79a8d6d900) 0
primary-for Apple (0x0x7f79a8c0e340)

Vtable for Orange
Orange::_ZTV6Orange: 14 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI6Orange)
16 (int (*)(...))Orange::~Orange
24 (int (*)(...))Orange::~Orange
32 (int (*)(...))Orange::foo
40 (int (*)(...))Orange::bar
48 (int (*)(...))Orange::baz
56 (int (*)(...))Orange::orange_bar
64 (int (*)(...))-24
72 (int (*)(...))(& _ZTI6Orange)
80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
96 (int (*)(...))Orange::_ZThn24_N6Orange3fooEv
104 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv

Class Orange
size=48 align=8
base size=48 base align=8
Orange (0x0x7f79a8d81770) 0
vptr=((& Orange::_ZTV6Orange) + 16)
Fruit (0x0x7f79a8d6de40) 0
primary-for Orange (0x0x7f79a8d81770)
Drug (0x0x7f79a8d6dea0) 24
vptr=((& Orange::_ZTV6Orange) + 80)

We will go through the class hierarchy information, including the virtual table information, for each class in detail.

1
2
3
4
5
6
7
8
Vtable for Drug
Drug::_ZTV4Drug: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Drug)
16 0
24 0
32 (int (*)(...))__cxa_pure_virtual
40 (int (*)(...))__cxa_pure_virtual

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 the Drug class pointer adjustment. This is particularly used for dynamically switching the vtable pointers between different base classes and derived classes using dynamic_cast. In this case, because Drug is just the base class and there is no need for pointer adjustment for Drug objects, so the offset-to-top value is 0. As we will see later, the offset-to-top value in the vtable for the Drug subobject in the classes derived from Drug might not be 0.
  • 8 (int (*)(...))(& _ZTI4Drug) is the typeinfo pointer to the type information of the Drug 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 the Drug class description is stored. As we will see later, the typeinfo pointer in the vtable for the Drug subobject in the classes derived from Drug will point to the buffer that stores the derived class description instead of the Drug class description.
  • 16 0 and 24 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 calling delete() on the object. The second destructor, called the deleting destructor, calls delete() 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 call delete(). Sometimes, we would like to only destroy the object without deallocating the memory. That’s why we have two destructors. Because the Drug 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 and 40 (int (*)(...))__cxa_pure_virtual are the pointers to the pure virtual functions Drug::foo() and Drug::baz() respectively which will be overridden by the derived class function implementations.
1
2
3
4
5
Class Drug
size=16 align=8
base size=12 base align=8
Drug (0x0x7f79a8d6d420) 0
vptr=((& Drug::_ZTV4Drug) + 16)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
Vtable for Fruit
Fruit::_ZTV5Fruit: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Fruit)
16 0
24 0
32 (int (*)(...))__cxa_pure_virtual
40 (int (*)(...))Fruit::bar

Class Fruit
size=24 align=8
base size=21 base align=8
Fruit (0x0x7f79a8d6d6c0) 0
vptr=((& Fruit::_ZTV5Fruit) + 16)

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
2
3
4
5
6
7
8
9
Vtable for Apple
Apple::_ZTV5Apple: 7 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Apple)
16 (int (*)(...))Apple::~Apple
24 (int (*)(...))Apple::~Apple
32 (int (*)(...))Apple::foo
40 (int (*)(...))Fruit::bar
48 (int (*)(...))Apple::apple_foo

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 the Fruit and Apple class pointer adjustment. Because the Apple class only has one base class, besides the vtable for the Fruit base class, the vtable for the Apple class is just extended from the vtable for the Fruit base class. Thus the Apple class and the Fruit 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 the Apple class.
  • 16 (int (*)(...))Apple::~Apple and 24 (int (*)(...))Apple::~Apple are the function pointers to complete object destructor and the deleting destructor. When an Apple class object is constructed, the Fruit base class constructor is first called followed by the Apple class constructor is called. When an Apple class object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, the Apple destructor is first called followed by the Fruit destructor is called. When an Apple class object is destroyed via an Apple class pointer or a Fruit class base pointer, the deleting destructor of the Apple class, which is different from the complete object destructor, will be called via the vtable. Inside the deleting destructor, after the the Apple class components are removed, the Fruit base class destructor will be called. Finally, the Apple class object is deallocated from the memory.
  • 32 (int (*)(...))Apple::foo is the pointer to the overriding functions for Fruit::foo. So even if it is a Fruit base class pointer pointing to an Apple class object, it can always find the overriding function Apple::foo correctly via the vtable.
  • 40 (int (*)(...))Fruit::bar is the function pointer to the function Fruit::bar defined in the Fruit base class since it has never been overridden.
  • 48 (int (*)(...))Apple::apple_foo is the pointer to the new virtual function Apple::apple_foo for the Apple class. It belongs to the Apple class vtable and is only accessible via an Apple class pointer, even though the Apple class vtable is just extended from the Fruit class vtable on the memory.
1
2
3
4
5
6
7
Class Apple
size=32 align=8
base size=28 base align=8
Apple (0x0x7f79a8c0e340) 0
vptr=((& Apple::_ZTV5Apple) + 16)
Fruit (0x0x7f79a8d6d900) 0
primary-for Apple (0x0x7f79a8c0e340)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vtable for Orange
Orange::_ZTV6Orange: 14 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI6Orange)
16 (int (*)(...))Orange::~Orange
24 (int (*)(...))Orange::~Orange
32 (int (*)(...))Orange::foo
40 (int (*)(...))Orange::bar
48 (int (*)(...))Orange::baz
56 (int (*)(...))Orange::orange_bar
64 (int (*)(...))-24
72 (int (*)(...))(& _ZTI6Orange)
80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
96 (int (*)(...))Orange::_ZThn24_N6Orange3fooEv
104 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv

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 the Fruit and Orange class pointer adjustment. For the primary base class Fruit and the Orange class, because their pointers both points to the beginning of the Orange class object, there is no need for pointer adjustment. Therefore, the offset-to-top value for the Fruit and the Orange class is 0.
  • 8 (int (*)(...))(& _ZTI6Orange) is the typeinfo pointer to the type information of the Orange class.
  • 16 (int (*)(...))Orange::~Orange and 24 (int (*)(...))Orange::~Orange are the function pointers to complete object destructor and the deleting destructor. When an Orange class object is constructed, the Fruit base class constructor is first called, followed by the Drug base class constructor is called, followed by the Orange class constructor is called. When an Orange class object is destructed, the order of destructor calls must be the reverse of the order of constructor calls. In our case, the Orange destructor is first called, followed by the Drug destructor is called, followed by the Fruit destructor is called. When an Orange class object is destroyed via an Orange class pointer or a Fruit class base pointer, The deleting destructor of the Orange class will be called via the vtable. Before the deleting destructor gets returned, the Drug and Fruit base class destructors are called and finally the Orange class object is deallocated from the memory.
  • 32 (int (*)(...))Orange::foo is the pointer to the overriding functions for Fruit::foo. So even if it is a Fruit base class pointer pointing to an Orange class object, it can always find the overriding function Orange::foo correctly via the vtable. Note that even though the Drug base class also has a virtual function Drug::foo that’s also overridden by Orange::foo, this Orange::foo does not belong to the Drug base class vtable and is not accessible via a Drug base class pointer.
  • 40 (int (*)(...))Orange::bar is the pointer to the overriding functions for Fruit::bar. So even if it is a Fruit base class pointer pointing to an Orange class object, it can always find the overriding function Orange::bar correctly via the vtable.
  • 48 (int (*)(...))Orange::baz is the pointer to the overriding functions for Drug::baz. This Orange::baz only belongs to the Orange class vtable and it only accessible via a Orange class pointer.
  • 56 (int (*)(...))Orange::orange_bar is the pointer to the new virtual function Orange::orange_bar for the Orange class. It belongs to the Orange class vtable and is only accessible via an Orange class pointer, even though the Orange class vtable is just extended from the Fruit class vtable on the memory.
  • 64 (int (*)(...))-24 is the offset-to-top value for the Drug class pointer adjustment. Suppose the address of a Drug pointer pointing to a Orange class object is x, then the address to the actual Orange class object is x - 24. Suppose the address of a Drug pointer pointing to a Drug class object is x, as we have seen in the Drug class vtable, the offset-to-top value is 0 instead of -24. So there is no point adjustment if the actual object is a Drug 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 the Orange class.
  • 80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev and 88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev are the function pointers to complete object destructor and the deleting destructor. They are almost the same as the 16 (int (*)(...))Orange::~Orange and 24 (int (*)(...))Orange::~Orange. The difference is that they belong to the Drug class vtable and can only be accessed by the Drug base class pointer. The implementation of 80 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev and 88 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev might involve the this pointer adjustment so that 16 (int (*)(...))Orange::~Orange and 24 (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 for Drug::foo. So even if it is a Drug base class pointer pointing to an Orange class object, it can always find the overriding function Orange::foo correctly via the vtable. It belongs to the Drug class vtable and can only be accessed by the Drug base class pointer.
  • 104 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv is the the pointer to the overriding functions for Drug::baz. So even if it is a Drug base class pointer pointing to an Orange class object, it can always find the overriding function Orange::baz correctly via the vtable. It belongs to the Drug class vtable and can only be accessed by the Drug base class pointer.
1
2
3
4
5
6
7
8
9
Class Orange
size=48 align=8
base size=48 base align=8
Orange (0x0x7f79a8d81770) 0
vptr=((& Orange::_ZTV6Orange) + 16)
Fruit (0x0x7f79a8d6de40) 0
primary-for Orange (0x0x7f79a8d81770)
Drug (0x0x7f79a8d6dea0) 24
vptr=((& Orange::_ZTV6Orange) + 80)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
$ pahole inheritance
class Orange : public Fruit, public Drug {
public:

/* class Fruit <ancestor>; */ /* 0 24 */

/* XXX last struct has 3 bytes of padding */

/* class Drug <ancestor>; */ /* 24 16 */

/* XXX last struct has 4 bytes of padding */
void Orange(class Orange *, const class Orange &);

void Orange(class Orange *, double, int, char, double);

virtual void ~Orange(class Orange *, int);

virtual void foo(class Orange *);

virtual void bar(class Orange *);

virtual void baz(class Orange *);

void get_weight(const class Orange *);

virtual void orange_bar(class Orange *);


double m_weight; /* 40 8 */
/* vtable has 4 entries: {
[2] = foo((null)),
[3] = bar((null)),
[4] = baz((null)),
[5] = orange_bar((null)),
} */
/* size: 48, cachelines: 1, members: 3 */
/* paddings: 2, sum paddings: 7 */
/* last cacheline: 48 bytes */

/* BRAIN FART ALERT! 48 bytes != 8 (member bytes) + 0 (member bits) + 0 (byte holes) + 0 (bit holes), diff = 320 bits */
};
class Fruit {
public:

void Fruit(class Fruit *, const class Fruit &);

int ()(void) * * _vptr.Fruit; /* 0 8 */
void Fruit(class Fruit *, double, int, char);

virtual void ~Fruit(class Fruit *, int);

virtual void foo(class Fruit *);

virtual void bar(class Fruit *);

double m_size; /* 8 8 */
int m_id; /* 16 4 */
char m_country; /* 20 1 */
/* vtable has 2 entries: {
[2] = foo((null)),
[3] = bar((null)),
} */
/* size: 24, cachelines: 1, members: 4 */
/* padding: 3 */
/* last cacheline: 24 bytes */
};
class Drug {
public:

void Drug(class Drug *, const class Drug &);

int ()(void) * * _vptr.Drug; /* 0 8 */
void Drug(class Drug *, int);

virtual void ~Drug(class Drug *, int);

virtual void foo(class Drug *);

virtual void baz(class Drug *);

int m_property; /* 8 4 */
/* vtable has 2 entries: {
[2] = foo((null)),
[3] = baz((null)),
} */
/* size: 16, cachelines: 1, members: 2 */
/* padding: 4 */
/* last cacheline: 16 bytes */
};
class Apple : public Fruit {
public:

/* class Fruit <ancestor>; */ /* 0 24 */

/* XXX last struct has 3 bytes of padding */
void Apple(class Apple *, const class Apple &);

void Apple(class Apple *, double, int, char, int);

virtual void ~Apple(class Apple *, int);

virtual void foo(class Apple *);

void get_color(const class Apple *);

virtual void apple_foo(class Apple *);


int m_color; /* 24 4 */
/* vtable has 2 entries: {
[2] = foo((null)),
[4] = apple_foo((null)),
} */
/* size: 32, cachelines: 1, members: 2 */
/* padding: 4 */
/* paddings: 1, sum paddings: 3 */
/* last cacheline: 32 bytes */

/* BRAIN FART ALERT! 32 bytes != 4 (member bytes) + 0 (member bits) + 0 (byte holes) + 0 (bit holes), diff = 192 bits */
};

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
2
3
4
5
  A
/ \
B C
\ /
D

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
2
3
4
5
A   A
| |
B C
\ /
D

Example

The following example created five classes Item, Fruit, Drug, Apple, and Orange.

1
2
3
4
5
Item
|
Fruit
|
Apple
1
2
3
4
5
    Item 
/ \
Fruit Drug
\ /
Orange

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.

virtual_inheritance.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Item
{
public:
Item(long long item_id) : m_item_id{item_id} {};
virtual ~Item() = default;
virtual void qux(){};
virtual void quux(){};
long long m_item_id;
};

class Drug : virtual public Item
{
public:
Drug(int property) : m_property{property}, Item{0LL} {};
virtual ~Drug() = default;
virtual void foo() = 0;
virtual void baz() = 0;
void qux() override{};
int m_property;
};

class Fruit : virtual public Item
{
public:
Fruit(double size, int id, char country)
: m_size{size}, m_id{id}, m_country{country}, Item{0LL} {};
virtual ~Fruit() = default;
virtual void foo() = 0;
virtual void bar(){};
void quux() override{};
double m_size;
int m_id;
char m_country;
};

class Apple : public Fruit
{
public:
Apple(double size, int id, char country, int color, long long item_id)
: Fruit{size, id, country}, m_color{color}, Item{item_id} {};
~Apple() = default;
void foo() override {}
void get_color() const {}
virtual void apple_foo() {}
int m_color;
};

class Orange : public Fruit, public Drug
{
public:
Orange(double size, int id, char country, double weight, long long item_id)
: Fruit{size, id, country}, Drug{4}, m_weight{weight}, Item{item_id} {};
~Orange() = default;
// Overriding Fruit virtual functions.
void foo() override {}
void bar() override {}
// Overriding Drug virtual functions.
void baz() override {}
// Orange non-virtual functions.
void get_weight() const {}
// Orange virtual functions.
virtual void orange_bar() {}
double m_weight;
};

int main()
{
long long const apple_item_id{1000LL};
double const apple_size{1.0};
int const apple_id{1};
char const apple_country{'c'};
int const apple_color{2};
long long const orange_item_id{1001LL};
double const orange_size{0.8};
int const orange_id{2};
char const orange_country{'n'};
double const orange_weight{0.6};
Apple apple{apple_size, apple_id, apple_country, apple_color,
apple_item_id};
Orange orange{orange_size, orange_id, orange_country, orange_weight,
orange_item_id};
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
$ g++ -g -fdump-lang-class virtual_inheritance.cpp -o virtual_inheritance
$ cat virtual_inheritance.cpp.001l.class
Vtable for Item
Item::_ZTV4Item: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Item)
16 (int (*)(...))Item::~Item
24 (int (*)(...))Item::~Item
32 (int (*)(...))Item::qux
40 (int (*)(...))Item::quux

Class Item
size=16 align=8
base size=16 base align=8
Item (0x0x7fa3f5d6d420) 0
vptr=((& Item::_ZTV4Item) + 16)

Vtable for Drug
Drug::_ZTV4Drug: 17 entries
0 16
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4Drug)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))__cxa_pure_virtual
56 (int (*)(...))Drug::qux
64 0
72 18446744073709551600
80 18446744073709551600
88 (int (*)(...))-16
96 (int (*)(...))(& _ZTI4Drug)
104 0
112 0
120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
128 (int (*)(...))Item::quux

VTT for Drug
Drug::_ZTT4Drug: 2 entries
0 ((& Drug::_ZTV4Drug) + 24)
8 ((& Drug::_ZTV4Drug) + 104)

Class Drug
size=32 align=8
base size=12 base align=8
Drug (0x0x7fa3f5c0e340) 0
vptridx=0 vptr=((& Drug::_ZTV4Drug) + 24)
Item (0x0x7fa3f5d6d780) 16 virtual
vptridx=8 vbaseoffset=-24 vptr=((& Drug::_ZTV4Drug) + 104)

Vtable for Fruit
Fruit::_ZTV5Fruit: 17 entries
0 24
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI5Fruit)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))Fruit::bar
56 (int (*)(...))Fruit::quux
64 18446744073709551592
72 0
80 18446744073709551592
88 (int (*)(...))-24
96 (int (*)(...))(& _ZTI5Fruit)
104 0
112 0
120 (int (*)(...))Item::qux
128 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv

VTT for Fruit
Fruit::_ZTT5Fruit: 2 entries
0 ((& Fruit::_ZTV5Fruit) + 24)
8 ((& Fruit::_ZTV5Fruit) + 104)

Class Fruit
size=40 align=8
base size=21 base align=8
Fruit (0x0x7fa3f5c0e478) 0
vptridx=0 vptr=((& Fruit::_ZTV5Fruit) + 24)
Item (0x0x7fa3f5d6dae0) 24 virtual
vptridx=8 vbaseoffset=-24 vptr=((& Fruit::_ZTV5Fruit) + 104)

Vtable for Apple
Apple::_ZTV5Apple: 18 entries
0 32
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI5Apple)
24 (int (*)(...))Apple::~Apple
32 (int (*)(...))Apple::~Apple
40 (int (*)(...))Apple::foo
48 (int (*)(...))Fruit::bar
56 (int (*)(...))Fruit::quux
64 (int (*)(...))Apple::apple_foo
72 18446744073709551584
80 0
88 18446744073709551584
96 (int (*)(...))-32
104 (int (*)(...))(& _ZTI5Apple)
112 (int (*)(...))Apple::_ZTv0_n24_N5AppleD1Ev
120 (int (*)(...))Apple::_ZTv0_n24_N5AppleD0Ev
128 (int (*)(...))Item::qux
136 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv

Construction vtable for Fruit (0x0x7fa3f5c0e5b0 instance) in Apple
Apple::_ZTC5Apple0_5Fruit: 17 entries
0 32
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI5Fruit)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))Fruit::bar
56 (int (*)(...))Fruit::quux
64 18446744073709551584
72 0
80 18446744073709551584
88 (int (*)(...))-32
96 (int (*)(...))(& _ZTI5Fruit)
104 0
112 0
120 (int (*)(...))Item::qux
128 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv

VTT for Apple
Apple::_ZTT5Apple: 4 entries
0 ((& Apple::_ZTV5Apple) + 24)
8 ((& Apple::_ZTC5Apple0_5Fruit) + 24)
16 ((& Apple::_ZTC5Apple0_5Fruit) + 104)
24 ((& Apple::_ZTV5Apple) + 112)

Class Apple
size=48 align=8
base size=28 base align=8
Apple (0x0x7fa3f5c0e548) 0
vptridx=0 vptr=((& Apple::_ZTV5Apple) + 24)
Fruit (0x0x7fa3f5c0e5b0) 0
primary-for Apple (0x0x7fa3f5c0e548)
subvttidx=8
Item (0x0x7fa3f5d6dde0) 32 virtual
vptridx=24 vbaseoffset=-24 vptr=((& Apple::_ZTV5Apple) + 112)

Vtable for Orange
Orange::_ZTV6Orange: 27 entries
0 48
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI6Orange)
24 (int (*)(...))Orange::~Orange
32 (int (*)(...))Orange::~Orange
40 (int (*)(...))Orange::foo
48 (int (*)(...))Orange::bar
56 (int (*)(...))Fruit::quux
64 (int (*)(...))Orange::baz
72 (int (*)(...))Orange::orange_bar
80 24
88 (int (*)(...))-24
96 (int (*)(...))(& _ZTI6Orange)
104 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
112 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
120 (int (*)(...))Orange::_ZThn24_N6Orange3fooEv
128 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv
136 (int (*)(...))Drug::qux
144 18446744073709551568
152 18446744073709551592
160 18446744073709551568
168 (int (*)(...))-48
176 (int (*)(...))(& _ZTI6Orange)
184 (int (*)(...))Orange::_ZTv0_n24_N6OrangeD1Ev
192 (int (*)(...))Orange::_ZTv0_n24_N6OrangeD0Ev
200 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
208 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv

Construction vtable for Fruit (0x0x7fa3f5c0e6e8 instance) in Orange
Orange::_ZTC6Orange0_5Fruit: 17 entries
0 48
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI5Fruit)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))Fruit::bar
56 (int (*)(...))Fruit::quux
64 18446744073709551568
72 0
80 18446744073709551568
88 (int (*)(...))-48
96 (int (*)(...))(& _ZTI5Fruit)
104 0
112 0
120 (int (*)(...))Item::qux
128 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv

Construction vtable for Drug (0x0x7fa3f5c0e750 instance) in Orange
Orange::_ZTC6Orange24_4Drug: 17 entries
0 24
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4Drug)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))__cxa_pure_virtual
56 (int (*)(...))Drug::qux
64 0
72 18446744073709551592
80 18446744073709551592
88 (int (*)(...))-24
96 (int (*)(...))(& _ZTI4Drug)
104 0
112 0
120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
128 (int (*)(...))Item::quux

VTT for Orange
Orange::_ZTT6Orange: 7 entries
0 ((& Orange::_ZTV6Orange) + 24)
8 ((& Orange::_ZTC6Orange0_5Fruit) + 24)
16 ((& Orange::_ZTC6Orange0_5Fruit) + 104)
24 ((& Orange::_ZTC6Orange24_4Drug) + 24)
32 ((& Orange::_ZTC6Orange24_4Drug) + 104)
40 ((& Orange::_ZTV6Orange) + 184)
48 ((& Orange::_ZTV6Orange) + 104)

Class Orange
size=64 align=8
base size=48 base align=8
Orange (0x0x7fa3f5d82c40) 0
vptridx=0 vptr=((& Orange::_ZTV6Orange) + 24)
Fruit (0x0x7fa3f5c0e6e8) 0
primary-for Orange (0x0x7fa3f5d82c40)
subvttidx=8
Item (0x0x7fa3f5da7600) 48 virtual
vptridx=40 vbaseoffset=-24 vptr=((& Orange::_ZTV6Orange) + 184)
Drug (0x0x7fa3f5c0e750) 24
subvttidx=24 vptridx=48 vptr=((& Orange::_ZTV6Orange) + 104)
Item (0x0x7fa3f5da7600) alternative-path

We will go through the class hierarchy information, including the virtual table information, for each class in detail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vtable for Item
Item::_ZTV4Item: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Item)
16 (int (*)(...))Item::~Item
24 (int (*)(...))Item::~Item
32 (int (*)(...))Item::qux
40 (int (*)(...))Item::quux

Class Item
size=16 align=8
base size=16 base align=8
Item (0x0x7fa3f5d6d420) 0
vptr=((& Item::_ZTV4Item) + 16)

Understanding the Item class is straightforward given we what have discussed in the previous sections.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vtable for Drug
Drug::_ZTV4Drug: 17 entries
0 16
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4Drug)
24 0
32 0
40 (int (*)(...))__cxa_pure_virtual
48 (int (*)(...))__cxa_pure_virtual
56 (int (*)(...))Drug::qux
64 0
72 18446744073709551600
80 18446744073709551600
88 (int (*)(...))-16
96 (int (*)(...))(& _ZTI4Drug)
104 0
112 0
120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
128 (int (*)(...))Item::quux

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 class Drug pointer, by adding the vbase-offset to the Drug class pointer, we could get the virtual base class pointer. Notice because the class Item is a virtual base for the Drug class, unlike the non-virtual inheritance, it’s not constructed at the offset 0 to the Drug class.
  • 64 0, 72 18446744073709551600, and 80 18446744073709551600 are the virtual-call-offsets or vcall-offsets for the virtual base Item class pointer. It’s used for adjusting the this pointer used in the virtual functions from the virtual base class. Because there are three virtual functions in the virtual base Item class, including Item::~Item, Item::qux, and Item::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-offset 80 18446744073709551600 is used for the virtual destructor Item::~Item, the last but one virtual-call-offset 72 18446744073709551600 is used for Item::qux, and the first virtual-call-offset 64 0 is used for Item::quux. This educative guess seems to be consistent for other classes as well. For example, the Item::qux virtual function has been overridden by the Drug::qux function, whereas the Item::quux virtual function has not. When the Drug::qux function is invoked via the virtual base Item class pointer through the 120 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv entry in the vtable, a derived class Drug object should be passed to the function. But the this pointer actually points to the virtual base Item class object. To get the pointer pointing to the Drug object, 72 18446744073709551600 is added to the this pointer. The 18446744073709551600 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 of 18446744073709551600 is 111111111111111111111111111111111111111111111111111111111110000. The MSB is 1, indicating a negative number. Inverting all the bits results in 000000000000000000000000000000000000000000000000000000000001111. Adding 1 to the inverted binary number gives 000000000000000000000000000000000000000000000000000000000010000. The resulting binary number, 000000000000000000000000000000000000000000000000000000000010000, is 16. The negated decimal value is 16. Then the original value 18446744073709551600 represents the decimal value -16. Now it makes sense, because this + (-16) will just point to the Drug object. Similarly, for the virtual destructor, since it has been overridden by the Drug class, the same vcall-offset has to be used for adjusting the this pointer used for the destructor so that it points to the Drug object. Finally, where is the vcall-offset 64 0 is used? Note that the Item::quux has not been overridden by the Drug class, an Item object should be used for the Item::quux function. Since this already points to the Item virtual base object, the offset is 0.

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
2
3
4
5
6
7
Class Drug
size=32 align=8
base size=12 base align=8
Drug (0x0x7fa3f5c0e340) 0
vptridx=0 vptr=((& Drug::_ZTV4Drug) + 24)
Item (0x0x7fa3f5d6d780) 16 virtual
vptridx=8 vbaseoffset=-24 vptr=((& Drug::_ZTV4Drug) + 104)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Vtable for Orange
Orange::_ZTV6Orange: 27 entries
0 48
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI6Orange)
24 (int (*)(...))Orange::~Orange
32 (int (*)(...))Orange::~Orange
40 (int (*)(...))Orange::foo
48 (int (*)(...))Orange::bar
56 (int (*)(...))Fruit::quux
64 (int (*)(...))Orange::baz
72 (int (*)(...))Orange::orange_bar
80 24
88 (int (*)(...))-24
96 (int (*)(...))(& _ZTI6Orange)
104 (int (*)(...))Orange::_ZThn24_N6OrangeD1Ev
112 (int (*)(...))Orange::_ZThn24_N6OrangeD0Ev
120 (int (*)(...))Orange::_ZThn24_N6Orange3fooEv
128 (int (*)(...))Orange::_ZThn24_N6Orange3bazEv
136 (int (*)(...))Drug::qux
144 18446744073709551568
152 18446744073709551592
160 18446744073709551568
168 (int (*)(...))-48
176 (int (*)(...))(& _ZTI6Orange)
184 (int (*)(...))Orange::_ZTv0_n24_N6OrangeD1Ev
192 (int (*)(...))Orange::_ZTv0_n24_N6OrangeD0Ev
200 (int (*)(...))Drug::_ZTv0_n32_N4Drug3quxEv
208 (int (*)(...))Fruit::_ZTv0_n40_N5Fruit4quuxEv
  • 0 48 is the vbase-offset for the Orange and Fruit class pointers.
  • 80 24 is the vbase-offset for the Drug class pointers.
  • 144 18446744073709551568, 152 18446744073709551592, and 160 18446744073709551568, which translate to 144 -48, 152 -24, and 160 -48 are the vcall-offsets for the Item::quux, Item::qux, and Item::~Item overriding functions. Because Item::quux is overridden by the Fruit::quux, the vcall-offset -48 will be used for adjusting the this pointer from pointing to an Item object to pointing to a Fruit object. This is the same for the Item::~Item which is overridden by the Orange::~Orange. Because Item::qux is overridden by the Drug::qux, the vcall-offset -24 will be used for adjusting the this pointer from pointing to an Item object to pointing to a Drug object. These offsets are also consistent with the offset-to-top values for the Drug and Item 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
2
3
4
5
6
7
8
9
10
11
12
13
Class Orange
size=64 align=8
base size=48 base align=8
Orange (0x0x7fa3f5d82c40) 0
vptridx=0 vptr=((& Orange::_ZTV6Orange) + 24)
Fruit (0x0x7fa3f5c0e6e8) 0
primary-for Orange (0x0x7fa3f5d82c40)
subvttidx=8
Item (0x0x7fa3f5da7600) 48 virtual
vptridx=40 vbaseoffset=-24 vptr=((& Orange::_ZTV6Orange) + 184)
Drug (0x0x7fa3f5c0e750) 24
subvttidx=24 vptridx=48 vptr=((& Orange::_ZTV6Orange) + 104)
Item (0x0x7fa3f5da7600) alternative-path

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
$ pahole virtual_inheritance
dwarf_expr: unhandled 0x12 DW_OP_ operation
dwarf_expr: unhandled 0x12 DW_OP_ operation
class Orange : public Fruit, public Drug {
public:

/* class Fruit <ancestor>; */ /* 0 40 */

/* XXX last struct has 19 bytes of padding */
/* XXX 65520 bytes hole, try to pack */

/* class Drug <ancestor>; */ /* 24 32 */

/* XXX last struct has 20 bytes of padding */
/* XXX 65520 bytes hole, try to pack */
void Orange(class Orange *, int, const void * *, const class Orange &);

void Orange(class Orange *, int, const void * *, double, int, char, double, long long int);

virtual void ~Orange(class Orange *, int, const void * *);

virtual void foo(class Orange *);

virtual void bar(class Orange *);

virtual void baz(class Orange *);

void get_weight(const class Orange *);

virtual void orange_bar(class Orange *);


double m_weight; /* 40 8 */

/* Force padding: */
double :64;
double :64;
/* vtable has 4 entries: {
[2] = foo((null)),
[3] = bar((null)),
[5] = baz((null)),
[6] = orange_bar((null)),
} */
/* size: 64, cachelines: 1, members: 3 */
/* sum members: 8, holes: 2, sum holes: 131040 */
/* padding: 16 */
/* paddings: 2, sum paddings: 39 */

/* BRAIN FART ALERT! 64 bytes != 8 (member bytes) + 0 (member bits) + 131040 (byte holes) + 0 (bit holes), diff = -1048000 bits */
};
class Fruit : virtual public Item {
public:

/* --- cacheline 67108863 boundary (4294967232 bytes) was 63 bytes ago --- */
/* class Item <ancestor>; */ /* 4294967295 16 */
void Fruit(class Fruit *, int, const void * *, const class Fruit &);

int ()(void) * * _vptr.Fruit; /* 0 8 */
void Fruit(class Fruit *, int, const void * *, double, int, char);

virtual void ~Fruit(class Fruit *, int, const void * *);

virtual void foo(class Fruit *);

virtual void bar(class Fruit *);

virtual void quux(class Fruit *);

double m_size; /* 8 8 */
int m_id; /* 16 4 */
char m_country; /* 20 1 */

/* Force padding: */
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
char :8;
/* vtable has 3 entries: {
[2] = foo((null)),
[3] = bar((null)),
[4] = quux((null)),
} */
/* size: 40, cachelines: 1, members: 5 */
/* padding: 19 */
/* last cacheline: 40 bytes */
};
class Drug : virtual public Item {
public:

/* --- cacheline 67108863 boundary (4294967232 bytes) was 63 bytes ago --- */
/* class Item <ancestor>; */ /* 4294967295 16 */
void Drug(class Drug *, int, const void * *, const class Drug &);

int ()(void) * * _vptr.Drug; /* 0 8 */
void Drug(class Drug *, int, const void * *, int);

virtual void ~Drug(class Drug *, int, const void * *);

virtual void foo(class Drug *);

virtual void baz(class Drug *);

virtual void qux(class Drug *);

int m_property; /* 8 4 */

/* Force padding: */
int :32;
int :32;
int :32;
int :32;
int :32;
/* vtable has 3 entries: {
[2] = foo((null)),
[3] = baz((null)),
[4] = qux((null)),
} */
/* size: 32, cachelines: 1, members: 3 */
/* padding: 20 */
/* last cacheline: 32 bytes */
};
class Apple : public Fruit {
public:

/* class Fruit <ancestor>; */ /* 0 40 */

/* XXX last struct has 19 bytes of padding */
/* XXX 65520 bytes hole, try to pack */
void Apple(class Apple *, int, const void * *, const class Apple &);

void Apple(class Apple *, int, const void * *, double, int, char, int, long long int);

virtual void ~Apple(class Apple *, int, const void * *);

virtual void foo(class Apple *);

void get_color(const class Apple *);

virtual void apple_foo(class Apple *);


int m_color; /* 24 4 */

/* Force padding: */
int :32;
int :32;
int :32;
int :32;
int :32;
/* vtable has 2 entries: {
[2] = foo((null)),
[5] = apple_foo((null)),
} */
/* size: 48, cachelines: 1, members: 2 */
/* sum members: 4, holes: 1, sum holes: 65520 */
/* padding: 20 */
/* paddings: 1, sum paddings: 19 */
/* last cacheline: 48 bytes */

/* BRAIN FART ALERT! 48 bytes != 4 (member bytes) + 0 (member bits) + 65520 (byte holes) + 0 (bit holes), diff = -523968 bits */
};
class Item {
public:

void Item(class Item *, const class Item &);

int ()(void) * * _vptr.Item; /* 0 8 */
void Item(class Item *, long long int);

virtual void ~Item(class Item *, int);

virtual void qux(class Item *);

virtual void quux(class Item *);

long long int m_item_id; /* 8 8 */
/* vtable has 2 entries: {
[2] = qux((null)),
[3] = quux((null)),
} */
/* size: 16, cachelines: 1, members: 2 */
/* last cacheline: 16 bytes */
} __attribute__((__packed__));

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 of T and non-virtual direct base classes of T.
  • 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 of T.
  • 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) for T.

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

Author

Lei Mao

Posted on

07-17-2023

Updated on

07-17-2023

Licensed under


Comments