最近在写B+Tree的时候突然发现对类节点的大小没算准,导致不知道固定大小的内存可以存几个节点。Google上得到了不少资料,先加以整理并加入一些自己的测试;一下都是linux环境输出的记过,windows vc++输出结果会不一样的,原文出处:C++ Class Size,windows相关的是我自己添加上去的。
涉及到c++中求类大小时需要特别注意一下几点
1.为类的非静态成员数据的类型大小之和.
2.有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针、虚继承、多重继承).
3.为了优化存取效率,进行的边缘调整.
4. 与类中的构造函数,析构函数以及其他的成员函数无关.
5. 私有继承,会去继承之前的私有成员变量么? 会...在内存中仍然分配相应的空间,只是在子类中是不可见的!
6. 在做多层次的继承类大小时某个子类的类大小总是等于父类的大小加上子类中数据成员和是否有虚函数,是否是虚继承等因素来决定。
那我们先看看一个例子:
#include<iostream.h>
class a {};
class b{};
class c:public a{
virtual void fun()=0;
};
class d:public b,public c{};
int main()
{
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
return 0;}
程序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
上面是在VC++6.0编译的结果,但是在Dev-C++和Code::Blocks下得出的结果是 sizeof( d ) = 4
为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类a,b明明是空类,它的大小应该为为0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1.
[cpp]
#include<iostream>
using namespace std;
class A
{
public:
virtual void aa(){}
private:
char k[3];
};
class B: public A
{
public:
virtual void bb(){}
};
int main()
{
cout<<"A's size is "<<sizeof(A)<<endl;
cout<<"B's size is "<<sizeof(B)<<endl;
return 0;
}
VS和gcc下执行结果:
A's size is 8
B's size is 8
说明:有虚函数的类有个virtual table(虚函数表),里面包含了类的所有虚函数,类中有个virtual table pointers,通常成为vptr指向这个virtual table,占用4个字节的大小。成员类B public继承于A,类B的虚函数表里实际上有两个虚函数A::aa()和B::bb(),类B的大小等于char k[3]的大小加上一个指向虚函数表指针vptr的大小,考虑内存对齐为8。但是这里有一点,当class B是public virtual A,这样的话sizeof(B)就是16除了需要一个指向虚继承父类的指针之外,由于父类的虚函数是aa(),子类的虚函数是bb(),所以好像是B里面建了两个虚表,一个是继承A类的aa(),一个是B类的bb(),如果A和B里面的虚函数都是aa的话,这个B的大小将会是12.
A's size is 8
B's size is 12
说明:类B里包含,继承的char k[3],继承的虚函数,类B的虚函数表里有A::aa(),因为是虚继承,还有一个指向父类的指针,该指针为指向虚基类的指针(Pointer to virtual base class)。考虑内存对齐,总大小为12。这里要特别指明一下,在windows下,B's size is 16,貌似在windows里虚继承还会有一个4字节的什么东西的这个应该和编译器有关系了。
像这种继承方式,在类C中会维护两个虚函数指针,第一个指向第一个基类的虚函数表(并且带上在类C中定义的虚函数),第二个指针指向第二个基类(B)的虚函数表...其他的类似。
综合例子:
#include <iostream>
using namespace std;
class A
{
char k[3];
public:
virtual void f(){};
};
class B : public virtual A
{
char i[3];
public:
virtual void f1(){};
};
class C: public virtual A{
//virtual void f(){};
char j[3];
public:
virtual void t(){};
};
class D: public B,public C
{
char g[3];
public:
virtual void s();
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
cout<<sizeof(D)<<endl;
return 0;
}
最后得到的结果是 8 16 16 28 (windows里是8 20 20 36,参照上述虚继承子类和父类续函数不一样导致的)
最主要的是sizeof(D): 4个char数组,对齐后是16,又是多重继承,那么有两个虚函数表的指针,又需要维护一分A类的指针,那么是16+4*2+4=28...采用虚继承,目的就是为了解决二义性和减小内存开销,所以在D中只维护一份A的指针便可。
在来插一句,有很多同学都觉得很奇怪,为什么类里面成员函数不占内存,其实成员函数和普通函数一样的都在内存,只不过分配在代码段而已,所以sizeof没有计算出来;