多重继承的内存分布.doc
《多重继承的内存分布.doc》由会员分享,可在线阅读,更多相关《多重继承的内存分布.doc(10页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、【精品文档】如有侵权,请联系网站删除,仅供学习与交流多重继承的内存分布.精品文档.指针的比较 再以上面Bottom类继承关系为例讨论,下面这段代码会打印Equal吗?1Bottom* b =newBottom();2Right* r = b;34if(r = b)5 printf(Equal!/n);先明确下这两个指针实际上是指向不同地址的,r指针实际上在b指针所指地址上偏移8字节,但是,这些C+内部细节不能告诉C+程序员,所以C+编译器在比较r和b时,会把r减去8字节,然后再来比较,所以打印出的值是Equal.多重继承 首先我们先来考虑一个很简单(non-virtual)的多重继承。看看下面
2、这个C+类层次结构。1classTop23public:4inta;5;67classLeft :publicTop89public:10intb;11;1213classRight :publicTop1415public:16intc;17;1819classBottom :publicLeft,publicRight2021public:22intd;23;24 用UML表述如下:注意到Top类实际上被继承了两次,(这种机制在Eiffel中被称作repeatedinheritance),这就意味着在一个bottom对象中实际上有两个a属性(attributes,可以通过bottom.Le
3、ft:a和bottom.Right:a访问)。那么Left、Right、Bottom在内存中如何分布的呢?我们先来看看简单的Left和Right内存分布: Right 类的布局和Left是一样的,因此我这里就没再画图了。刺猬注意到上面类各自的第一个属性都是继承自Top类,这就意味着下面两个赋值语句:1Left* left =newLeft();2Top* top = left;left和top实际上是指向两个相同的地址,我们可以把Left对象当作一个Top对象(同样也可以把Right对象当Top对象来使用)。但是Botom对象呢?GCC是这样处理的: 但是现在如果我们upcast一个Botto
4、m指针将会有什么结果?1Bottom* bottom =newBottom();2Left* left = bottom;这段代码运行正确。这是因为GCC选择的这种内存布局使得我们可以把Bottom对象当作Left对象,它们两者(Left部分)正好相同。但是,如果我们把Bottom对象指针upcast到Right对象呢?1Right* right = bottom;如果我们要使这段代码正常工作的话,我们需要调整指针指向Bottom中相应的部分。通过调整,我们可以用right指针访问Bottom对象,这时Bottom对象表现得就如Right对象。但是bottom和right指针指向了不同的内存地
5、址。最后,我们考虑下:1Top* top = bottom;恩,什么结果也没有,这条语句实际上是有歧义(ambiguous)的,编译器会报错: error: Top is an ambiguous base of Bottom。其实这两种带有歧义的可能性可以用如下语句加以区分:1Top* topL = (Left*) bottom;2Top* topR = (Right*) bottom;这两个赋值语句执行之后,topL和left指针将指向同一个地址,同样topR和right也将指向同一个地址。虚拟继承为了避免上述Top类的多次继承,我们必须虚拟继承类Top。1class Top23publi
6、c:4inta;5;67class Left : virtual public Top89public:10intb;11;1213class Right : virtual public Top1415public:16intc;17;1819class Bottom : public Left, public Right2021public:22intd;23;24上述代码将产生如下的类层次图(其实这可能正好是你最开始想要的继承方式)。对于程序员来说,这种类层次图显得更加简单和清晰,不过对于一个编译器来说,这就复杂得多了。我们再用Bottom的内存布局作为例子考虑,它可能是这样的:这种内存
7、布局的优势在于它的开头部分(Left部分)和Left的布局正好相同,我们可以很轻易地通过一个Left指针访问一个Bottom对象。不过,我们再来考虑考虑Right:1Right* right = bottom;虚拟继承为了避免上述Top类的多次继承,我们必须虚拟继承类Top。1class Top23public:4inta;5;67class Left : virtual public Top89public:10intb;11;1213class Right : virtual public Top1415public:16intc;17;1819class Bottom : public
8、Left, public Right2021public:22intd;23;24上述代码将产生如下的类层次图(其实这可能正好是你最开始想要的继承方式)。对于程序员来说,这种类层次图显得更加简单和清晰,不过对于一个编译器来说,这就复杂得多了。我们再用Bottom的内存布局作为例子考虑,它可能是这样的:这种内存布局的优势在于它的开头部分(Left部分)和Left的布局正好相同,我们可以很轻易地通过一个Left指针访问一个Bottom对象。不过,我们再来考虑考虑Right:1Right* right = bottom;这里我们应该把什么地址赋值给right指针呢?理论上说,通过这个赋值语句,我们可
9、以把这个right指针当作真正指向一个Right对象的指针(现在指向的是Bottom)来使用。但实际上这是不现实的!一个真正的Right对象内存布局和Bottom对象Right部分是完全不同的,所以其实我们不可能再把这个upcasted的bottom对象当作一个真正的right对象来使用了。而且,我们这种布局的设计不可能还有改进的余地了。这里我们先看看实际上内存是怎么分布的,然后再解释下为什么这么设计。上图有两点值得大家注意。第一点就是类中成员分布顺序是完全不一样的(实际上可以说是正好相反)。第二点,类中增加了vptr指针,这些是被编译器在编译过程中插入到类中的(在设计类时如果使用了虚继承,虚
10、函数都会产生相关vptr)。同时,在类的构造函数中会对相关指针做初始化,这些也是编译器完成的工作。Vptr指针指向了一个“virtualtable”。在类中每个虚基类都会存在与之对应的一个vptr指针。为了给大家展示virtualtable作用,考虑下如下代码。1Bottom* bottom = new Bottom();2Left* left = bottom;3intp = left-a;第二条的赋值语句让left指针指向和bottom同样的起始地址(即它指向Bottom对象的“顶部”)。我们来考虑下第三条的赋值语句。1movlleft, %eax# %eax=left2movl(%eax
11、), %eax# %eax=left.vptr.Left3movl(%eax), %eax# %eax=virtualbaseoffset4addlleft, %eax# %eax=left+virtualbaseoffset5movl(%eax), %eax# %eax=left.a6movl%eax,p #p=left.a总结下,我们用left指针去索引(找到)virtualtable,然后在virtualtable中获取到虚基类的偏移(virtualbaseoffset,vbase),然后在left指针上加上这个偏移量,这样我们就获取到了Bottom类中Top类的开始地址。从上图中,我们
12、可以看到对于Left指针,它的virtualbaseoffset是20,如果我们假设Bottom中每个成员都是4字节大小,那么Left指针加上20字节正好是成员a的地址。我们同样可以用相同的方式访问Bottom中Right部分。1Bottom* bottom = new Bottom();2Right* right = bottom;3intp = right-a;right指针就会指向在Bottom对象中相应的位置。这里对于p的赋值语句最终会被编译成和上述left相同的方式访问a。唯一的不同是就是vptr,我们访问的vptr现在指向了virtualtable另一个地址,我们得到的virtua
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 多重 继承 内存 分布
限制150内