Memory Layout for Multiple and Virtual Inheritance
2011-11-27 11:54
701 查看
[From]
CodeProject, By Al-Farooque Shubho | 2 Aug 2010
Warning. This article is rather technical and assumes agood knowledge of C++ and some assembly language.
In this article we explain the object layout implemented bygcc for multiple and virtual inheritance. Although in anideal world C++ programmers should not need to know these details ofthe compiler internals, unfortunately the way multiple (and especiallyvirtual)
inheritance is implemented has various non-obviousconsequences for writing C++ code (in particular, fordowncasting pointers, usingpointers
to pointers, and the invocationorder of
constructors for virtualbases). If you understand how multiple inheritance is implemented,you will be able anticipate these consequences and deal with them inyour code. Also, it is useful to understand the cost of using virtualinheritance if you care about
efficiency. Finally, it is interesting:-)
Using a UML diagram, we can represent this hierarchy as
![](http://phpcompiler.org/articles/virtualinheritance/nonvirtual.png)
Note that
How are
Note that the first attribute is the attribute inherited from
Now what happens when we upcast a
This works out nicely. Because of the memory layout, we can treatan object of type
For this to work, we have to adjust the pointer value to make it point to the corresponding section of the
After this adjustment, we can access
what would happen when we do
Right, nothing at all. This statement is ambiguous: the compilerwill complain
The two possibilities can be disambiguated using
After these two assignments,
This yields the following hierarchy (which is perhaps what you expected in the first place)
![](http://phpcompiler.org/articles/virtualinheritance/virtual.png)
while this may seem more obvious and simpler from a programmer'spoint of view, from the compiler's point of view, this is vastly morecomplicated. Consider the layout of
The advantage of this layout is that the first part of the layout collides with the layout of
Which address do we assign to
different, and we can thus no longer access a“real”
The solution is non-trivial. We will show the solution first andthen explain it.
![](http://phpcompiler.org/articles/virtualinheritance/vtable.png)
You should note two things in this diagram. First, the order ofthe fields is completely different (in fact, it is approximately thereverse). Second, there are these new
whennecessary (when using virtual inheritance, or when using virtualfunctions). The compiler also inserts code into the constructor toinitialise these pointers.
The
The second assignment makes
In words, we use
From
ddb8
the diagram, you can see that the virtual base offset for
With this setup, we can access the
The assignment to
weobtain is 12, which is correct (verify!). We can summarise this visually:
![](http://phpcompiler.org/articles/virtualinheritance/vtable3.png)
Of course, the point of the exercise was to be able to access real
![](http://phpcompiler.org/articles/virtualinheritance/vtable2.png)
Now we can access a
and a simple attribute lookup in an object now needs twoindirections through the virtual table (although compileroptimizations can reduce that cost somewhat).
then simply be implemented by subtracting the sameoffset. And indeed, this is the case for non-virtual inheritance.However, virtual inheritance (unsurprisingly!) introduces anothercomplication.
Suppose we extend our inheritance hierarchy with the followingclass.
The hierarchy now looks like
![](http://phpcompiler.org/articles/virtualinheritance/virtual2.png)
Now consider the following code.
The following diagram shows the layout of
Now consider how to implement the static cast from
It can't be done! The necessary offsetdepends on the runtime type of
Since we need runtime information, we need to use a dynamic castinstead:
However, the compiler is still unhappy:
The problem is that a dynamic cast (as well as use of
is an integer (
This change necessitates a
![](http://phpcompiler.org/articles/virtualinheritance/vtable4.png)
(Of course, the other classes get a similar new
This function
cast can be executed. (The -1 parameterindicates that the relationship between
We have seen previously what theeffect is of
(the value of
Should the compiler accept this? A quick test will show that thecompiler will complain:
Why? Suppose the compiler would accept the assignment of
![](http://phpcompiler.org/articles/virtualinheritance/doublepointers.png)
So,
This is essentially the same assignment as the assignment to
to
![](http://phpcompiler.org/articles/virtualinheritance/doublepointers2.png)
This is correct as long as we access the
So, in summary, even if
ofyour virtual superclasses (independent of how far up the tree theyare), the compiler will automatically insert a call to their defaultconstructors.
This can lead to some unexpected results. Consider the same classhierarchy again we have been considering so far, extended withconstructors:
(We consider the non-virtual case first.) What would you expectthis to output:
You would probably expect (and get)
However, now consider the virtual case (where we inherit virtuallyfrom
Why? If you trace the execution of the constructors, you will find
As explained above, the compiler has inserted a call to thedefault constructor in
hasalready been initialised and the constructor does not get invoked.
To avoid this situation, you should explicitly call theconstructor of your virtual base(s):
Bear in mind that the two addresses are not actually equal(
the two addresses are considered equal.
Casting to
Finally, we consider what happens we can cast an object to
wondering what the offset to top field is. It is theoffset from the
C++ ABISummary, the
Itanium C++ABI (despite the name, this document is referenced in aplatform-independent context; in particular, thestructureof the vtables is detailed here). Thelibstdc++implementation
of dynamic casts, as well RTTI and nameunmangling/demangling, is defined intinfo.cc.
[2] The libstdc++ website, in particular the section on theC++ Standard Library API.
[3] C++: Under the Hood by Jan Gray.
[4] Chapter 9, “Multiple Inheritance” of Thinking in C++ (volume 2) byBruce Eckel. The author has made this book available fordownload.
CodeProject, By Al-Farooque Shubho | 2 Aug 2010
Warning. This article is rather technical and assumes agood knowledge of C++ and some assembly language.
In this article we explain the object layout implemented bygcc for multiple and virtual inheritance. Although in anideal world C++ programmers should not need to know these details ofthe compiler internals, unfortunately the way multiple (and especiallyvirtual)
inheritance is implemented has various non-obviousconsequences for writing C++ code (in particular, fordowncasting pointers, usingpointers
to pointers, and the invocationorder of
constructors for virtualbases). If you understand how multiple inheritance is implemented,you will be able anticipate these consequences and deal with them inyour code. Also, it is useful to understand the cost of using virtualinheritance if you care about
efficiency. Finally, it is interesting:-)
Multiple Inheritance
First we consider the relatively simple case of (non-virtual)multiple inheritance. Consider the following C++ class hierarchy.class Top { public: int a; }; class Left : public Top { public: int b; }; class Right : public Top { public: int c; }; class Bottom : public Left, public Right { public: int d; };
Using a UML diagram, we can represent this hierarchy as
![](http://phpcompiler.org/articles/virtualinheritance/nonvirtual.png)
Note that
Topis inheritedfrom twice (this is known asrepeated inheritance inEiffel). This means that an object
bottomof type
Bottomwill havetwo attributes called
a(accessed as
bottom.Left::aand
bottom.Right::a).
How are
Left,
Rightand
Bottomlaid out in memory? We show the simplest casefirst.
Leftand
Righthave the followingstructure:
|
|
Top. This means that after the following two assignments
Left* left = new Left(); Top* top = left;
leftand
topcan point to the exact sameaddress, and we can treat the
Leftobject as if it were a
Topobject (and obviously a similar thing happens for
Right). What about
Bottom?gccsuggests
Bottom |
---|
Left::Top::a |
Left::b |
Right::Top::a |
Right::c |
Bottom::d |
Bottompointer?
Bottom* bottom = new Bottom(); Left* left = bottom;
This works out nicely. Because of the memory layout, we can treatan object of type
Bottomas if it were an object of type
Left, because the memory layout of both classes coincide.However, what happens when we upcast to
Right?
Right* right = bottom;
For this to work, we have to adjust the pointer value to make it point to the corresponding section of the
Bottomlayout:
Bottom | |
---|---|
Left::Top::a | |
Left::b | |
right ![]() | Right::Top::a |
Right::c | |
Bottom::d |
bottomthroughthe
rightpointer as a normal
Rightobject;however,
bottomand
rightnowpoint todifferent memory locations. For completeness' sake,consider
what would happen when we do
Top* top = bottom;
Right, nothing at all. This statement is ambiguous: the compilerwill complain
error: `Top' is an ambiguous base of `Bottom'
The two possibilities can be disambiguated using
Top* topL = (Left*) bottom; Top* topR = (Right*) bottom;
After these two assignments,
topLand
leftwill point to the same address, as will
topRand
right.
Virtual Inheritance
To avoid the repeated inheritance ofTop, we must inherit virtually from
Top:
class Top { public: int a; }; class Left : virtual public Top { public: int b; }; class Right : virtual public Top { public: int c; }; class Bottom : public Left, public Right { public: int d; };
This yields the following hierarchy (which is perhaps what you expected in the first place)
![](http://phpcompiler.org/articles/virtualinheritance/virtual.png)
while this may seem more obvious and simpler from a programmer'spoint of view, from the compiler's point of view, this is vastly morecomplicated. Consider the layout of
Bottomagain. One(non) possibility is
Bottom |
---|
Left::Top::a |
Left::b |
Right::c |
Bottom::d |
Left, and we can thus access a
Bottomeasily through a
Leftpointer. However, what are we going to do with
Right* right = bottom;
Which address do we assign to
right? After thisassignment, we should be able to use
rightas if it werepointing to a regular
Rightobject. However, this isimpossible! The memory layout of
Rightitself iscompletely
different, and we can thus no longer access a“real”
Rightobject in the same way as anupcasted
Bottomobject. Moreover, no other (simple)layout for
Bottomwill work.
The solution is non-trivial. We will show the solution first andthen explain it.
![](http://phpcompiler.org/articles/virtualinheritance/vtable.png)
You should note two things in this diagram. First, the order ofthe fields is completely different (in fact, it is approximately thereverse). Second, there are these new
vptrpointers.These attributes are automatically inserted by the compiler
whennecessary (when using virtual inheritance, or when using virtualfunctions). The compiler also inserts code into the constructor toinitialise these pointers.
The
vptrs (virtual pointers) index a “virtualtable”. There is a
vptrfor every virtual base ofthe class. To see how the virtual table (vtable) is used,consider the following C++ code.
Bottom* bottom = new Bottom(); Left* left = bottom;
int p = left->a;
The second assignment makes
leftpoint to the sameaddress as
bottom(i.e., it points to the“top” of the
Bottomobject). We consider thecompilation of the last assignment (slightly simplified):
movl left, %eax # %eax = left movl (%eax), %eax # %eax = left.vptr.Left movl (%eax), %eax # %eax = virtual base offset addl left, %eax # %eax = left + virtual base offset movl (%eax), %eax # %eax = left.a movl %eax, p # p = left.a
In words, we use
leftto index the virtual table andobtain the “virtual base offset” (vbase). Thisoffset is then added to
left, which is then used to indexthe
Topsection of the
Bottomobject.
From
ddb8
the diagram, you can see that the virtual base offset for
Leftis 20; if you assume that all the fields in
Bottomare 4 bytes, you will see that adding 20 bytes to
leftwill indeed point to the
afield.
With this setup, we can access the
Rightpart thesame way. After
Bottom* bottom = new Bottom(); Right* right = bottom; int p = right->a;
rightwill point to the appropriate part of the
Bottomobject:
Bottom | |
---|---|
vptr.Left | |
Left::b | |
right ![]() | vptr.Right |
Right::c | |
Bottom::d | |
Top::a |
pcan now be compiled in theexact same way as we did previously for
Left. Theonly difference is that the
vptrwe access now points toa different part of the virtual table: the virtual base offset
weobtain is 12, which is correct (verify!). We can summarise this visually:
![](http://phpcompiler.org/articles/virtualinheritance/vtable3.png)
Of course, the point of the exercise was to be able to access real
Rightobjects the same way as upcasted
Bottomobjects. So, we have to introduce
vptrs in the layout of
Right(and
Left) too:
![](http://phpcompiler.org/articles/virtualinheritance/vtable2.png)
Now we can access a
Bottomobject through a
Rightpointer without further difficulty. However, thishas come at rather large expense: we needed to introduce virtualtables, classes needed to be extended with one or more virtualpointers,
and a simple attribute lookup in an object now needs twoindirections through the virtual table (although compileroptimizations can reduce that cost somewhat).
Downcasting
As we have seen, casting a pointer of typeDerivedClassto a pointer of type
SuperClass(in other words, upcasting) may involve adding an offset to thepointer. One might be tempted to think that downcasting (going theother way) can
then simply be implemented by subtracting the sameoffset. And indeed, this is the case for non-virtual inheritance.However, virtual inheritance (unsurprisingly!) introduces anothercomplication.
Suppose we extend our inheritance hierarchy with the followingclass.
class AnotherBottom : public Left, public Right { public: int e; int f; };
The hierarchy now looks like
![](http://phpcompiler.org/articles/virtualinheritance/virtual2.png)
Now consider the following code.
Bottom* bottom1 = new Bottom(); AnotherBottom* bottom2 = new AnotherBottom(); Top* top1 = bottom1; Top* top2 = bottom2; Left* left = static_cast<Left*>(top1);
The following diagram shows the layout of
Bottomand
AnotherBottom, and shows where
topispointing after the last assignment.
|
|
top1to
left, while taking into account thatwe do not know whether
top1is pointing to an object oftype
Bottomor an object of type
AnotherBottom.
It can't be done! The necessary offsetdepends on the runtime type of
top1(20 for
Bottomand 24 for
AnotherBottom). Thecompiler will complain:
error: cannot convert from base `Top' to derived type `Left' via virtual base `Top'
Since we need runtime information, we need to use a dynamic castinstead:
Left* left = dynamic_cast<Left*>(top1);
However, the compiler is still unhappy:
error: cannot dynamic_cast `top' (of type `class Top*') to type `class Left*' (source type is not polymorphic)
The problem is that a dynamic cast (as well as use of
typeid) needs runtime type information about the objectpointed to by
top1. However, if you look at the diagram,you will see that all we have at the location pointed to by
top1
is an integer (
a). The compiler did notinclude a
vptr.Topbecause it did not think that wasnecessary. To force the compiler to include this
vptr, wecan add a virtual destructor to
Top:
class Top { public: virtual ~Top() {} int a; };
This change necessitates a
vptrfor
Top. The new layout for
Bottomis
![](http://phpcompiler.org/articles/virtualinheritance/vtable4.png)
(Of course, the other classes get a similar new
vptr.Topattribute). The compiler now inserts a librarycall for the dynamic cast:
left = __dynamic_cast(top1, typeinfo_for_Top, typeinfo_for_Left, -1);
This function
__dynamic_castis defined inlibstdc++ (the corresponding header file iscxxabi.h); armed with the type information for
Top,
Leftand
Bottom(through
vptr.Top), the
cast can be executed. (The -1 parameterindicates that the relationship between
Leftand
Topis presently unknown). For details, refer to theimplementation intinfo.cc.
Concluding Remarks
Finally, we tie a couple of loose ends.(In)variance of Double Pointers
This is were it gets slightly confusing, although it is ratherobvious when you give it some thought. We consider an example. Assumethe class hierarchy presented in the last section (Downcasting).We have seen previously what theeffect is of
Bottom* b = new Bottom(); Right* r = b;
(the value of
bgets adjusted by 8 bytes before it isassigned to
r, so that it points to the
Rightsection of the
Bottomobject). Thus,we can legally assign a
Bottom*to a
Right*.What about
Bottom**and
Right**?
Bottom** bb = &b; Right** rr = bb;
Should the compiler accept this? A quick test will show that thecompiler will complain:
error: invalid conversion from `Bottom**' to `Right**'
Why? Suppose the compiler would accept the assignment of
bbto
rr. We can visualise the result as:
![](http://phpcompiler.org/articles/virtualinheritance/doublepointers.png)
So,
bband
rrboth point to
b, and
band
rpoint to theappropriate sections of the
Bottomobject. Now considerwhat happens when we assign to
*rr(note that the type of
*rris
Right*, so this assignment isvalid):
*rr = b;
This is essentially the same assignment as the assignment to
rabove. Thus, the compiler will implement it the sameway! In particular, it will adjust the value of
bby 8bytes before it assigns it to
*rr. But
*rrpointed
to
b! If we visualise the result again:
![](http://phpcompiler.org/articles/virtualinheritance/doublepointers2.png)
This is correct as long as we access the
Bottomobject through
*rr, but as soon as we access it through
bitself, all memory references will be off by 8 bytes — obviously a very undesirable situation.
So, in summary, even if
*aand
*barerelated by some subtyping relation,
**aand
**barenot.
Constructors of Virtual Bases
The compiler must guarantee that all virtual pointers of anobject are properly initialised. In particular, it guarantees that theconstructor for all virtual bases of a class get invoked, and getinvoked only once. If you don't explicitly call the constructorsofyour virtual superclasses (independent of how far up the tree theyare), the compiler will automatically insert a call to their defaultconstructors.
This can lead to some unexpected results. Consider the same classhierarchy again we have been considering so far, extended withconstructors:
class Top { public: Top() { a = -1; } Top(int _a) { a = _a; } int a; }; class Left : public Top { public: Left() { b = -2; } Left(int _a, int _b) : Top(_a) { b = _b; } int b; }; class Right : public Top { public: Right() { c = -3; } Right(int _a, int _c) : Top(_a) { c = _c; } int c; }; class Bottom : public Left, public Right { public: Bottom() { d = -4; } Bottom(int _a, int _b, int _c, int _d) : Left(_a, _b), Right(_a, _c) { d = _d; } int d; };
(We consider the non-virtual case first.) What would you expectthis to output:
Bottom bottom(1,2,3,4);
printf("%d %d %d %d %d\n", bottom.Left::a, bottom.Right::a,bottom.b, bottom.c, bottom.d);
You would probably expect (and get)
1 1 2 3 4
However, now consider the virtual case (where we inherit virtuallyfrom
Top). If we make that single change, and run theprogram again, we instead get
-1 -1 2 3 4
Why? If you trace the execution of the constructors, you will find
Top::Top() Left::Left(1,2) Right::Right(1,3) Bottom::Bottom(1,2,3,4)
As explained above, the compiler has inserted a call to thedefault constructor in
Bottom, before the execution ofthe other constructors. Then when
Lefttries to call itssuperconstructor (
Top), we find that
Top
hasalready been initialised and the constructor does not get invoked.
To avoid this situation, you should explicitly call theconstructor of your virtual base(s):
Bottom(int _a, int _b, int _c, int _d): Top(_a), Left(_a,_b), Right(_a,_c) { d = _d; }
Pointer Equivalence
Once again assuming the same (virtual) class hierarchy, would youexpect this to print “Equal”?Bottom* b = new Bottom(); Right* r = b;
if(r == b)
printf("Equal!\n");
Bear in mind that the two addresses are not actually equal(
ris off by 8 bytes). However, that should be completelytransparent to the user; so, the compiler actually subtracts the 8bytes from
rbefore comparing it to
b;thus,
the two addresses are considered equal.
Casting to void*
Finally, we consider what happens we can cast an object tovoid*. The compiler must guarantee that a pointer cast to
void*points to the “top” of the object.Using the vtable, this is actually very easy to implement. You mayhave been
wondering what the offset to top field is. It is theoffset from the
vptrto the top of the object. So, a castto
void*can be implemented using a single lookup in thevtable. Make sure to use a dynamic cast, however, thus:
dynamic_cast<void*>(b);
References
[1] CodeSourcery, inparticular theC++ ABISummary, the
Itanium C++ABI (despite the name, this document is referenced in aplatform-independent context; in particular, thestructureof the vtables is detailed here). Thelibstdc++implementation
of dynamic casts, as well RTTI and nameunmangling/demangling, is defined intinfo.cc.
[2] The libstdc++ website, in particular the section on theC++ Standard Library API.
[3] C++: Under the Hood by Jan Gray.
[4] Chapter 9, “Multiple Inheritance” of Thinking in C++ (volume 2) byBruce Eckel. The author has made this book available fordownload.
相关文章推荐
- Memory Layout for Multiple and Virtual Inheritance (转载--By Edsko de Vries, January 2006)
- Memory Layout for Multiple and Virtual Inheritance
- Memory Layout for Multiple and Virtual Inheritance
- Memory Layout for Multiple and Virtual Inheritance (一) (部分翻译)
- C++ 多继承和虚继承的内存布局(Memory Layout for Multiple and Virtual Inheritance)
- C++ 多继承和虚继承的内存布局(Memory Layout for Multiple and Virtual Inheritance )
- non-virtual thunk for Virtual Function in multiple inheritance
- non-virtual thunk for Virtual Function in multiple inheritance
- MDB: virtual and physical memory map for both kernel and application
- Windows CE Virtual Memory Layout for Debugging
- C++ Virtual Inheritance Memory Layout
- virtual memory layout and how to get it by the correspoinding functuon
- Windows CE Virtual Memory Layout for Debugging
- Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI.
- Memory Layout for Multiple and Virtual Inheritance (By Edsko de Vries, January 2006)
- System and method for parallel execution of memory transactions using multiple memory models, including SSO, TSO, PSO and RMO
- Tracking metrics for both multiple targets and single target
- 内存分布和栈空间---Memory Layout And The Stack
- Effective Java 17 Design and document for inheritance or else prohibit it
- Multiple inheritance and the this pointer