怎么计算C++继承、虚继承、虚函数类的大小?
怎么计算c++继承、虚继承、虚函数类的大小?
一、真空类
c++代码
classcnull
{
};
长度:1
内存结构:
评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。
二、空类
c++代码
classcnull2
{
public:
cnull2(){printf("construct/n");}
~cnull2(){printf("desctruct/n");}
voidfoo(){printf("foo/n");}
};
长度:1
内存结构:
评注:同真空类差不多,内部的成员函数并不会影响类大小。
三、简单类
c++代码
classconemember
{
public:
conemember(intivalue=0){m_ione=ivalue;};
private:
intm_ione;
};
长度:4
内存结构:
00 00 00 00 //m_ione
评注:成员数据才影响类大小。
四、简单继承
c++代码
classctwomember:publicconemember
{
private:
intm_itwo;
};
长度:8
内存结构:
00 00 00 00 //m_ione
cc cc cc cc //m_itwo
评注:子类成员接在父类成员之后。
五、再继承
c++代码
classcthreemember:publicctwomember
{
public:
cthreemember(intivalue=10){m_ithree=ivalue;};
private:
intm_ithree;
};
长度:12
内存结构:
00 00 00 00 //m_ione
cc cc cc cc //m_itwo
0a 00 00 00 //m_ithree
评注:孙类成员接在子类之后,再再继承就依此类推了。
六、多重继承
c++代码
classclassa
{
public:
classa(intivalue=1){m_ia=ivalue;};
private:
intm_ia;
};
classclassb
{
public:
classb(intivalue=2){m_ib=ivalue;};
private:
intm_ib;
};
classclassc
{
public:
classc(intivalue=3){m_ic=ivalue;};
private:
intm_ic;
};
classccomplex:publicclassa,publicclassb,publicclassc
{
public:
ccomplex(intivalue=4){m_icomplex=ivalue;};
private:
intm_icomplex;
};
长度:16
内存结构:
01 00 00 00 //a
02 00 00 00 //b
03 00 00 00 //c
04 00 00 00 //complex
评注:也是父类成员先出现在前边,我想这都足够好理解。
七、复杂一些的继承
不写代码了,怕读者看了眼花,改画图。
长度:32
内存结构:
01 00 00 00 //a
02 00 00 00 //b
03 00 00 00 //c
04 00 00 00 //complex
00 00 00 00 //onemember
cc cc cc cc //twomember
0a 00 00 00 //threemember
05 00 00 00 //verycomplex
评注:还是把自己的成员放在最后。
只要没涉及到“虚”(virtual),我想没什么难点,不巧的是“虚”正是我们要研究的内容。
八、趁热打铁,看“虚继承”
c++代码
classctwomember:virtualpublicconemember
{
private:
intm_itwo;
};
长度:12
内存结构:
e8 2f 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针
cc cc cc cc // m_itwo
00 00 00 00 // m_ione(虚基类数据成员)
评注:virtual让长度增加了4,其实是多了一个指针,关于这个指针,确实有些复杂,别的文章有具体分析,这里就不岔开具体讲了,可认为它指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。
九、“闭合”虚继承,看看效果
长度:24
内存结构:
14 30 42 00 //classb的虚基类偏移量表指针
02 00 00 00 //m_ib
c4 2f 42 00 //classc的虚基类偏移量表指针
03 00 00 00 //m_ic
04 00 00 00 //m_icomplex
01 00 00 00 //m_ia
评注:和预料中的一样,虚基类的成员m_ia只出现了一次,而且是在最后边。当然了,更复杂的情况要比这个难分析得多,但虚继承不是我们研究的重点,我们只需要知道:虚继承利用一个“虚基类偏移量表指针”来使得虚基类即使被重复继承也只会出现一次。
十、看一下关于static成员
c++代码
classcstaticnull
{
public:
cstaticnull(){printf("construct/n");}
~cstaticnull(){printf("desctruct/n");}
staticvoidfoo(){printf("foo/n");}
staticintm_ivalue;
};
长度:1
内存结构:(同cnull2)
评注:可见static成员不会占用类的大小,static成员的存在区域为静态区,可认为它们是“全局”的,只是不提供全局的访问而已,这跟c的static其实没什么区别。
十一、带一个虚函数的空类
c++代码
classcvirtualnull
{
public:
cvirtualnull(){printf("construct/n");}
~cvirtualnull(){printf("desctruct/n");}
virtualvoidfoo(){printf("foo/n");}
};
长度:4
内存结构:
00 31 42 00 //指向虚函数表的指针(虚函数表后面简称“虚表”)
00423100:(虚表)
41 10 40 00 //指向虚函数foo的指针
00401041:
e9 78 02 00 00 e9 c3 03 … //函数foo的内容(看不懂)
评注:带虚函数的类长度就增加了4,这个4其实就是个指针,指向虚函数表的指针,上面这个例子中虚表只有一个函数指针,值就是“0x00401041”,指向的这个地址就是函数的入口了。
十二、继承带虚函数的类
c++代码
classcvirtualderived:publiccvirtualnull
{
public:
cvirtualderived(){m_ivd=0xff;};
~cvirtualderived(){};
private:
intm_ivd;
};
长度:8
内存结构:
3c 50 42 00 //虚表指针
ff 00 00 00 //m_ivd
0042503c:(虚表)
23 10 40 00 //指向虚函数foo的指针,如果这时候创建一个cvirtualnull对象,会发现它的虚表的内容跟这个一样
评注:由于父类带了虚函数,子类就算没有显式声明虚函数,虚表还是存在的,虚表存放的位置跟父类不同,但内容是同的,也就是对父类虚表的复制。
十三、子类有新的虚函数
c++代码
classcvirtualderived:publiccvirtualnull
{
public:
cvirtualderived(){m_ivd=0xff;};
~cvirtualderived(){};
virtualvoidfoo2(){printf("foo2/n");};
private:
intm_ivd;
};
长度:8
内存结构:
24 61 42 00 //虚表指针
ff 00 00 00 //m_ivd
00426124:(虚表)
23 10 40 00
50 10 40 00
评注:虚表还是只有一张,不会因为增加了新的虚函数而多出另一张来,新的虚函数的指针将添加在复制了的虚表的后面。
十四、当纯虚函数(pure function)出现时
c++代码
classcpurevirtual
{
virtualvoidfoo()=0;
};
classcderivepv:publiccpurevirtual
{
voidfoo(){printf("vd:foo/n");};
};
长度:4(cpurevirtual),4(cderivepv)
内存结构:
cpurevirtual:
(不可实例化)
cderivepv:
28 50 42 00 //虚表指针
00425028:(虚表)
5a 10 40 00 //指向foo的函数指针
评注:带纯虚函数的类不可实例化,因此列不出其“内存结构”,由其派生类实现纯虚函数。我们可以看到cderivepv虽然没有virtual声明,但由于其父类带virtual,所以还是继承了虚表,如果cderivepv有子类,还是这个道理。
十五、虚函数类的多重继承
前面提到:(子类的虚表)不会因为增加了新的虚函数而多出另一张来,但如果有多重继承的话情况就不是这样了。下例中你将看到两张虚表。
大小:24
内存结构
f8 50 42 00 //虚表指针
01 00 00 00 //m_ia
02 00 00 00 //m_ib
e8 50 42 00 //虚表指针
03 00 00 00 //m_ic
04 00 00 00 //m_icomplex
004250f8:(虚表)
5a 10 40 00 //fooa
55 10 40 00 //foob
64 10 40 00 //foocomplex
004250e8:(虚表)
5f 10 40 00 //fooc
评注:子类的虚函数接在第一个基类的虚函数表的后面,所以b接在a后面,complex接在b后面。基类依次出现,子类成员接在最后面,所以m_icomplex位于最后面。
十六、包含虚函数类的虚继承
c++代码
classvirtualinheritance
{
chark[3];
public:
virtualvoidaa(){};
};
classsonclass1:publicvirtualvirtualinheritance
{
charj[3];
public:
virtualvoidbb(){};
};
classsonclass2:publicvirtualsonclass1
{
charf[3];
public:
virtualvoidcc(){};
};
intmain()
{
cout
return0;
}
输出的结果:
visio studio: 8,20,32
gcc: 8,16,24
评注:
对于virtualinheritance类,大小为8, 没有异议,他有个虚表指针vtp_virtualinheritanc;
对于sonclass1类:
在visio studio 编译器下,大小为20。由于是虚拟继承,又有自己的虚函数,所以先拥有一个自己的虚函数指针vpt_sonclass1,大小为4,指向自己的虚表;还要有一个char[3],大小为4;为了实现虚拟继承,首先sonclass1加入了一个指向其父类的虚类指针,记作vtp_sonclass1_virtualinheritanc,大小为4;然后在加上父类的所有大小8,所以总共是20字节。
在gcc编译器下,大小为16,没有计算子类中指向父类的虚类指针vtp_sonclass1_virtualinheritanc的大小。
对于sonclass2:
在visio studio环境下,大小为32。和上面一样,子类拥有char[3],大小为4字节,因为是虚继承,还有自己的虚函数,所以拥有自己的一个虚表指针,vtp_sonclass2,大小为4字节。然后还有一个指向父类的虚类指针vtp_sonclass2_sonclass
1,大小为4。最后加上其父类的总大小20,所以总的大小为4+4+4+20=32;
在gcc环境下,没有计算虚类指针的大小,即4+4+16=24。
17、包含虚函数的多重普通、虚拟混合继承
c++代码
classvirtualinheritance
{
chark[3];
public:
virtualvoidaa(){};
};
classsonclass1:publicvirtualvirtualinheritance
{
charj[3];
public:
virtualvoidbb(){};
};
classvirtualinheritance2
{
charl[3];
public:
virtualvoiddd(){};
};
classsonclass2:publicvirtualsonclass1,publicvirtualinheritance2
{
charf[3];
public:
virtualvoidcc(){};
};
intmain()
{
cout
return0;
}
评注:此时sonclass2的大小变成36。和16不同的是,此时sonclass2是多重继承,其中一个是虚继承,一个普通继承,他的大小在visio studio中变成36,相比16增加了4,这刚好是char l[3]的大小,因为耸sonclass2已经有了一个虚表,所以在他原有的虚表中多一条记录即可。
18、包含虚函数的多重虚拟继承
c++代码
classvirtualinheritance
{
chark[3];
public:
virtualvoidaa(){};
};
classsonclass1:publicvirtualvirtualinheritance
{
charj[3];
public:
virtualvoidbb(){};
};
classvirtualinheritance2
{
charl[3];
public:
virtualvoiddd(){};
};
classsonclass2:publicvirtualsonclass1,publicvirtualvirtualinheritance2
{
charf[3];
public:
virtualvoidcc(){};
};
intmain()
{
cout
return0;
}
评注:此时sonclass2的大小变成40。与17不同的是,sonclass2的多重继承都是虚拟继承。sonclass2的大小由以下几部分构成:
1.自己本身的大小,char[3] 大小为4,一个虚函数,所以有个指向虚表的指针,大小为4,所以自身大小总的为8;
2.虚拟继承sonclass1,因为虚拟继承所以有个虚类指针ptr_sonclass2_sonclass1,大小为4,而sonclass1的大小为20,所以虚拟继承sonclass1的大小为24;
3.虚拟继承virtualinheritance2,一个虚类指针ptr_sonclass2_virtualinheritance2=ptr_sonclass2_sonclass1+偏移量,该指针和ptr_sonclass2_sonclass1公用一个指针,只是偏移量不同,所以大小为0(即使再多继承几个virtual class,这个指针的大小只算 一次),而virtualinheritance2的大小为8,所以总的大小为8。
所以40=8+24+8
总结:
1,普通单继承,只需将自身成员变量的大小加上父类大小(父类中 有虚函数,子类中不管有没有)若父类没有虚函数,则子类大小需要加上指向虚表的指针大小。
2,普通多继承,若几个父类都有虚表,则子类与第一个父类公用一个虚表指针,其他有几个有虚函数的父类则就有几个虚表指针。
3,虚拟单继承,此时若子类有虚函数则加上一个自身的虚表指针的大小,(若没有则不加)再加上自身的成员变量大小,还要加上一个虚类指针ptr_sonclass_fatherclass,最后加上父类的大小。
4,多重虚拟继承,此时若子类有虚函数则加上一个自身的虚表指针的大小,(若没有则不叫)再加上自身的成员变量大小,还要加上 一个公用的虚类指针(不管有几个虚拟父类,只加一个),在加上所有父类的大小。
5、普通、虚拟混合多继承,此时子类的大小为自身大小(若子类或普通父类有虚函数,则为成员变量+虚表指针大小;若都没虚函数,则就为成员变量大小),加上一个虚类指针大小,在加上虚拟父类的大小,在加上普通父类的大小(除虚表指针,因为它和子类公用一个虚表指针)。
上一篇: C语言实现单链表面试题(进阶篇)