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.
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.)
memory layout of a multiple-inherited object in C++
The use of virtual
is redundant in D1
.
From C++11, §10.3¶2:
If a virtual member function
vf
is declared in a classBase
and in a classDerived
, derived directly or indirectly fromBase
, a member functionvf
with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (or absence of same) asBase::vf
is declared, thenDerived::vf
is also virtual (whether or not it is so declared) and it overrides111Base::vf
.
111) A function with the same name but a different parameter list (Clause 13) as a virtual function is not necessarily virtual and does not override. The use of the
virtual
specifier in the declaration of an overriding function is legal but redundant (has empty semantics). Access control (Clause 11) is not considered in determining overriding.
Thus, the memory layout (which is what the question seems to be about) is the same for D
and D1
. Obviously, different types will have different virtual tables.
C++ Memory Layout: Questions about multiple inheritance, virtual destructors, and virtual function tables
- What is the meaning of the content in the red box?
- 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
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
Adding List Items or Nodes in Linked List
Casting Double Array to a Struct of Doubles
Opengl Gl_Polygon Concave Polygon Doesn't Color In
How to Emulate _Mm256_Loadu_Epi32 with Gcc or Clang
C++ Sizeof(Array) Return Twice the Array's Declared Length
Best Introduction to C++ Template Metaprogramming
How to Fix .Pch File Missing on Build
How Would You Implement a Basic Event-Loop
Why Is F(I = -1, I = -1) Undefined Behavior
When to Make a Type Non-Movable in C++11