c++2.0以后全面支持虚函数与虚继承,这两个特性的引入为c++增强了不少功能,也引入了不少烦恼。虚函数与虚继承有哪些特性,今天就不记录了,如果能搞了解一下编译器是如何实现虚函数和虚继承,它们在类的内存空间中又是如何布局的,却可以对c++的了解深入不少。这段时间花了一些时间了解这些玩意,搞得偶都
,不过总算有些收获,嘿嘿。
先看一段代码
class a
{
virtual aa(){};
};
class b : public virtual a
{
char j[3]; //加入一个变量是为了看清楚class中的vfptr放在什么位置
public:
virtual bb(){};
};
class c : public virtual b
{
char i[3];
public:
virtual cc(){};
};
这次先不给结果,先分析一下,也好加强一下印象。
1、对于class a,由于只有一个虚函数,那么必须得有一个对应的虚函数表,来记录对应的函数入口地址。同时在class a的内存空间中之需要有个vfptr_a指向该表。sizeof(a)也很容易确定,为4。
2、对于class b,由于class b虚基础了class a,同时还拥有自己的虚函数。那么class b中首先拥有一个vfptr_b,指向自己的虚函数表。还有char j[3],做一次alignment,一般大小为4。可虚继承该如何实现咧?
this is 啊 problem!偶之前是不晓得的,还好c++ object model上有介绍。首先要通过加入一个虚l类指针(记vbptr_b_a)来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。sizeof(b)= 4+4+4+4=16(vfptr_b、char j[3]做alignment、vbptr_b_a和class a)。
3、在接着是class c了。class c首先也得有个vfptr_c,然后是char i[3],然后是vbptr_c_b,然后是class b,所以sizeof(c)=4+4+4+16=28(vfptr_c、char i[3]做alignment、vbptr_c_a和class b)。
在vc 6.0下写了个程序,把上面几个类的大小打印出来,果然结果为4、16、28。hoho搞定!真的搞定了?也许经过上面的分析,虽然每个类具体的内存布局还不大清楚,但其中的内容应该不会错了。嘿嘿,在没跟踪时偶确实也是这么想的,但结果却是……![]()
vc中虚继承的内存布局——单继承
画了个图,简单表示一下我跟踪后的结果

虚基础之单继承时的内存布局图
class a的情况太简单,没问题。从class b的内存布局图可以得出下面的结论。
1、vf_ptr b放在了类的首部,那么如果要想直接拿memcpy完成类的复制是很危险的,用struct也是不行的。改天再深入学习一下struct 和class的区别,可以看出这里的差别来。
2、vbtbl_ptr_b,为什么不是先前我描述的vbptr_b_a呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class b的虚类表(嗯,俺自个儿起的名字,实在是学艺不精)。看看vb table,vb table有两项,第一项为fffffffc,这一项的值可能没啥意义,可能是为了保证虚类表不为空吧。第二项为8,看起来像是class b中的class a相对该vbtbl_ptr_b的位移,也就是一个offset。类似的方法在c++ object model(p121)有介绍,可以去看看。
class c的内存布局就比较复杂了,不过它的内存布局也更一步说明我对vbtbl_ptr_b中的内容,也就是虚类表的理解是正确的。不过值得关注的是class b中的class a在布局时被移到前面去了,虽然整个大小没变,但这样一来如果做这样的操作 c c; b *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class b的位置既可赋值给b。但是在构建class c时会复杂一些,后面的使用还是非常简单的,效率也比较高。class a的内存布局被前移可能是考虑倒c的虚继承顺序吧
。
结论
1、vc在编译时会把vfptr放到类的头部;
2、vc采用虚表指针(vbtbl_ptr)来确定某个类所继承的虚类。
3、vc会重新调整虚继承的父类在子类中内存布局。(具体规则还不清楚)
4、vc中虚类表中的第一项是无意义的,可能是为了保证sizeof(虚类表)!=0;后面的内容为父类在子类中相对该虚类表指针的偏移量。
目前看来虚继承在单一继承时的内存布局还是比较清晰的,不过多重继承呢?这就太bt了,简单的多重继承还没弄清楚呢,再来个虚继承,岂不只有
。以后等俺有兴趣的时候,在找这事做吧。