三十二、C++内存布局,对象大小计算、虚函数虚继承对类内存模型的影响

来源:互联网 发布:gh0st源码 编辑:程序博客网 时间:2024/04/29 07:25
一、类大小的影响因素
          我们知道,对象的大小是受一下因素影响:
  • 成员变量
  • 虚函数(产生虚函数表)
  • 虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)
对象大小跟成员函数,静态函数和静态数据无关。
二、类大小遵循结构体对齐原则
          我们在这里讲过结构体内存对齐的原则,这里我们在通过例子来看结构体对齐是如何影响对象的大小的。首先先温习一下结构体对齐的方法:

 (1)、如何对齐

    a)第一个数据成员放在offset为0的位置

    b)其它成员对齐至min(sizeof(member),#pragma pack所指定的值)的整数倍。

    c)整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。

    d)#pragma pack(n) 来修改,#pragma pack() 取消修改

根据上面的方法,假设#pragma pack的值为2,我们看下这个结构体该有多大:

struct Test{int a;char c;};
int a放在结构体的首部,占4个字节,对齐数为min(4,2)为2,char c大小为1,假设#pragma pack为2,对齐数就是min(1,1)结果为1,对齐在1的整数倍就是4的位置,最后整个结构体对齐至最大对齐数也就是2的整数倍。结果为6个字节。当#pragma pack的值为1,4,8时同样可以按照这个规则计算。内存结构如下:



三、虚函数对类的大小的影响
          虚函数会在对象中增加一个指向虚表的指针vptr。实例:
#include <iostream>using namespace std;class BB{public:virtual void vfbb(){cout<<"BB::vfbb"<<endl;}virtual void vfbb2(){cout<<"BB::vfbb2"<<endl;}int bb_;};int main(void){cout<<sizeof(BB)<<endl;return 0;}
类BB包含一个虚表指针和一个int成员,在32位机器上内存模型如下:

四、虚继承对类的大小的影响
          虚继承对对象内存造成的影响在这里已经探讨过。这里在探讨一下当虚继承和虚函数结合起来是如何影响对象内存的。实例:
#include <iostream>using namespace std;class BB{public:virtual void vfbb(){cout<<"BB::vfbb"<<endl;}virtual void vfbb2(){cout<<"BB::vfbb2"<<endl;}int bb_;};class B1 : virtual public BB{public:virtual void vfb1(){cout<<"B1::vfb1"<<endl;}int b1_;};typedef void (*FUNC)();int main(void){cout<<sizeof(B1)<<endl;long** p;FUNC fun;B1 b1;p = (long**)&b1;cout<<&b1<<endl;cout<<&b1.b1_<<endl;cout<<&b1.bb_<<endl;cout<<p[1][0]<<endl;cout<<p[1][1]<<endl;fun = (FUNC)p[0][0];fun();fun = (FUNC)p[3][0];fun();fun = (FUNC)p[3][1];fun();cout<<endl;return 0;}
打印结果:

根据结果我们可以分析出B1的对象模型如下:

由图可以看出,B1大小为20个字节,跟打印结果相同。
五、钻石型的虚拟多重继承对类内存的影响
          在虚拟多继承中,虚基类在派生类中只有一份拷贝。那么派生类是如何实现只有一份拷贝的呢?在访问虚基类的时候,又是如何找到的虚拟类的呢?我们通过一个例子探讨下钻石型的虚拟多重继承中派生类的对象模型。示例:
#include <iostream>using namespace std;class BB{public:virtual void vfbb(){cout<<"BB::vfbb"<<endl;}virtual void vfbb2(){cout<<"BB::vfbb2"<<endl;}int bb_;};class B1 : virtual public BB{public:virtual void vfb1(){cout<<"B1::vfb1"<<endl;}int b1_;};class B2 : virtual public BB{public:virtual void vfb2(){cout<<"B2::vfb2"<<endl;}int b2_;};class DD : public B1, public B2{public:virtual void vfdd(){cout<<"DD::vfdd"<<endl;}int dd_;};typedef void (*FUNC)();int main(void){cout<<sizeof(DD)<<endl;long** p;FUNC fun;DD dd;p = (long**)&dd;cout<<&dd<<endl;cout<<&dd.dd_<<endl;cout<<&dd.bb_<<endl;cout<<&dd.b1_<<endl;cout<<&dd.b2_<<endl;fun = (FUNC)p[0][0];fun();fun = (FUNC)p[0][1];fun();fun = (FUNC)p[3][0];fun();fun = (FUNC)p[7][0];fun();fun = (FUNC)p[7][1];fun();cout<<p[1][0]<<endl;cout<<p[1][1]<<endl;cout<<p[4][0]<<endl;cout<<p[4][1]<<endl;return 0;}
打印结果:

根据结果分析出类DD的对象模型:


从图中可以看出,编译器为了节约内存,把类DD的虚表和基类B1的虚表合并了。

0 0
原创粉丝点击