Virtual Tables and Memory Layout in Multiple Virtual Inheritance

Virtual tables and memory layout in multiple virtual inheritance

Virtual bases are very different from ordinary bases. Remember that "virtual" means "determined at runtime" -- thus the entire base subobject must be determined at runtime.

Imagine that you are getting a B & x reference, and you are tasked to find the A::a member. If the inheritance were real, then B has a superclass A, and thus the B-object which you are viewing through x has an A-subobject in which you can locate your member A::a. If the most-derived object of x has multiple bases of type A, then you can only see that particular copy which is the subobject of B.

But if the inheritance is virtual, none of this makes sense. We don't know which A-subobject we need -- this information simply doesn't exist at compile time. We could be dealing with an actual B-object as in B y; B & x = y;, or with a C-object like C z; B & x = z;, or something entirely different that derives virtually from A many more times. The only way to know is to find the actual base A at runtime.

This can be implemented with one more level of runtime indirection. (Note how this is entirely parallel to how virtual functions are implemented with one extra level of runtime indirection compared to non-virtual functions.) Instead of having a pointer to a vtable or base subobject, one solution is to store a pointer to a pointer to the actual base subobject. This is sometimes called a "thunk" or "trampoline".

So the actual object C z; may look as follows. The actual ordering in memory is up to the compiler and unimportant, and I've suppressed vtables.

+-+------++-+------++-----++-----+
|T| B1 ||T| B2 || C || A |
+-+------++-+------++-----++-----+
| | |
V V ^
| | +-Thunk-+ |
+--->>----+-->>---| ->>-+
+-------+

Thus, no matter whether you have a B1& or a B2&, you first look up the thunk, and that one in turn tells you where to find the actual base subobject. This also explains why you cannot perform a static cast from an A& to any of the derived types: this information simply doesn't exist at compile time.

For a more in-depth explanation, take a look at this fine article. (In that description, the thunk is part of the vtable of C, and virtual inheritance always necessitates the maintenance of vtables, even if there are no virtual functions anywhere.)

Object layout in case of virtual functions and multiple inheritance

The memory layout and the vtable layout depend on your compiler. Using my gcc for instance, they look like this:

sizeof(int) == 4
sizeof(A) == 8
sizeof(B) == 8
sizeof(C) == 20

Note that sizeof(int) and the space needed for the vtable pointer can also vary from compiler to compiler and platform to platform. The reason why sizeof(C) == 20 and not 16 is that gcc gives it 8 bytes for the A subobject, 8 bytes for the B subobject and 4 bytes for its member int c.


Vtable for C
C::_ZTV1C: 6u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI1C)
8 A::funA
12 (int (*)(...))-0x00000000000000008
16 (int (*)(...))(& _ZTI1C)
20 B::funB

Class C
size=20 align=4
base size=20 base align=4
C (0x40bd5e00) 0
vptr=((& C::_ZTV1C) + 8u)
A (0x40bd6080) 0
primary-for C (0x40bd5e00)
B (0x40bd60c0) 8
vptr=((& C::_ZTV1C) + 20u)

Using virtual inheritance

class C : public virtual A, public virtual B

the layout changes to


Vtable for C
C::_ZTV1C: 12u entries
0 16u
4 8u
8 (int (*)(...))0
12 (int (*)(...))(& _ZTI1C)
16 0u
20 (int (*)(...))-0x00000000000000008
24 (int (*)(...))(& _ZTI1C)
28 A::funA
32 0u
36 (int (*)(...))-0x00000000000000010
40 (int (*)(...))(& _ZTI1C)
44 B::funB

VTT for C
C::_ZTT1C: 3u entries
0 ((& C::_ZTV1C) + 16u)
4 ((& C::_ZTV1C) + 28u)
8 ((& C::_ZTV1C) + 44u)

Class C
size=24 align=4
base size=8 base align=4
C (0x40bd5e00) 0
vptridx=0u vptr=((& C::_ZTV1C) + 16u)
A (0x40bd6080) 8 virtual
vptridx=4u vbaseoffset=-0x0000000000000000c vptr=((& C::_ZTV1C) + 28u)
B (0x40bd60c0) 16 virtual
vptridx=8u vbaseoffset=-0x00000000000000010 vptr=((& C::_ZTV1C) + 44u)

Using gcc, you can add -fdump-class-hierarchy to obtain this information.

C++ Memory Layout: Questions about multiple inheritance, virtual destructors, and virtual function tables

  1. What is the meaning of the content in the red box?
  2. Does the above relate to "C++ trunk"?

The symbols you highlighed are mangled, you can use some demangle tools, like c++filt

> c++filt _ZThn16_N6Derive6f_b2_1Ev
non-virtual thunk to Derive::f_b2_1()

As to your rest questions, you could refer to What is a 'thunk'? this SO question and answers.

It's a part of ABI, you could refer to Itanium C++ ABI

C++ Member and vtable order in diamond (multiple) virtual inheritance

what is the correct ordering of member variables and vtable pointers?

There is no "correct ordering". This is not specified in the C++ standard. Each compiler is free to arrange the memory layout of this class hierarchy in any fashion that's compliant with the C++ standard.

A compiler may choose the layout of the class, in this situation, according to some fixed set of rules. Or, a compiler may try to optimize amongst several possibilities and pick a layout that can take advantage of hardware-related factors, like preferred alignment of objects, to try to minimize any required padding. This is entirely up to the compiler.

Furthermore: if you review the text of the C++ standard you will not find even a single mention of anything called a "vtable". It's not there. This is just the most common implementation mechanism for virtual class methods and virtual inheritance. Instead of a pointer, it would be perfectly compliant with the C++ standard for a C++ compiler to use a two-byte index into a table, instead of an entire pointer -- into a table containing records that define each particular class's virtual properties. This would work perfectly fine as long as the number of all classes with virtual properties does not exceed 65536.

In other words: you have no guarantees, whatsoever, what this class's layout will be, in memory, or what its size is. It is not specified in the C++ standard and is entirely implementation-defined.

How does vtable handle multiple inheritance?

The class will have 2 pointers to vtables, one to its implementation of Genius and one to its implementation of CoolDude. When casting to a base class, the the returned pointer will differ from the original by the offset of the vtable(and other members) or the base class.



Related Topics



Leave a reply



Submit