C++类所占用的内存空间
前言:C++类所占用的内存空间实际上是指类的实例所占用的内存空间。其大小是由类中的成员变量决定的(静态成员变量除外),由于成员函数放到代码区由类的各个实例共享,故成员函数对类占用内存大小没有影响。具体地说,类占用内存大小由以下三个方面决定:
1. 非静态成员变量的内存占用之和
2. 考虑内存对其的问题;
3. 虚函数产生的额外内存开销,即虚函数表指针(Virtual Table Pointer);
1.类的大小与什么有关系?
与类大小有关的因素:普通成员变量,虚函数,继承(单一继承,多重继承,重复继承,虚拟继承)
与类大小无关的因素:静态成员变量,静态成员函数及普通成员函数
2.空类
空类即什么都没有的类,按上面的说法,照理说大小应该是0,但是,空类的大小为1,因为空类可以实例化,实例化必然在内存中占有一个位置,因此,编译器为其优化为一个字节大小。
某类继承自空类:
class base
{
};
class derived:public base
{
private:
int a;
}
此时,derived类的大小为4,derived类的大小是自身int成员变量的大小,至于为什么没有加上父类base的大小1是因为空白基优化的问题,在空基类被继承后,子类会优化掉基类的1字节的大小,节省了空间大小,提高了运行效率。
3.一般类的大小(注意内存对齐)
具体实例如下:
1. C++空类所占用的内存空间
class A
{
public:
A();
~A();
};
cout<<sizeof(A)<<endl;//结果是1,而不是0
原因解释:C++标准中规定,任何两个类实例不能有相同的地址,假设构造了一个空类实例的数组,如果类实例的大小为0,则数组中各类实例具有相同的地址,与标准相悖。
2. 含有虚函数的类所占用的内存空间
class B :public A
{
public:
B();
~B();
int GetVal() //函数对类的大小没有影响
{
return 1;
}
virtual int GetNum();//有虚函数表,就有虚函数指针,所以是4个字节
virtual int GetIndex();//虽然虚函数有多个,但只有一个Vtpr
};
cout<<sizeof(B)<<endl;//结果是4
原因解释:含有虚函数的类都要维护一个虚函数表(Virtual Table),每个类的实例通过虚函数指针(Virtual Table Pointer)访问虚函数.而任何指针的大小都是4个字节。
3. 含有虚函数和其它成员变量的类所占用的内存空间
class A
{
public:
A();
~A();
private:
char b[2];//占用2个字节,空闲6个字节
virtual test(); //虚函数,分配4个字节的Vptr
char f[6];
double c;
/* char d;*/
};
cout<<sizeof(A)<<endl;//结果为24个字节
原因解释:不管虚函数放在什么位置都和成员变量单独计算内存空间,但一次内存分配的基准是一样的!以成员变量基础类型中最大的那个为准。char b[2]实际使用了2个字节,分配了8个字节,故空闲了6个字节。虚函数指针Vptr占用4个字节,但却不使用上述空闲了6个字节,而是以double为基准分配了8个字节,char f[6]占用上述空闲的6个字节,最后为double c分配了8个字节,共24个字节。即Vptr和成员变量在内存中单独放,不凑合!
4. 含有多种数据类型的成员变量
class C
{
public:
C();
~C();
private:
int a[3];
char Name[6];
double b;//以基类型占用内存最大的作为对齐的基准
};
cout<<sizeof(C)<<endl;//结果是32
原因解释:类的内存分配和结构体一样遵循内存对齐原则,即取成员变量原始类型,如int,double等作为一次内存分配的基准,在Class C中double类型占用8个字节,为最大的,故内存一次分配8个字节;系统给int a[3]分配16个字节,空闲了4个字节,无法容纳第二个变量char Name[6];故又分配了8个字节,最后给double b;分配了8个字节,共32个字节;
5. 派生类继承父类的情况
class A //基类
{
public:
A();
~A();
private:
virtual GetNum();//虚函数,4个字节的指针
char b[5];//占用5个字节,实际分配了8个字节
double c;
/* char d;*/
};
class B :public A //派生类
{
public:
B();
~B();
private:
int a[3];
char Name[3];//占用了3个字节,实际分配了8个字节
double b;//以最大的作为基准
};
cout<<sizeof(A)<<endl;//结果为24
cout<<sizeof(B)<<endl;//结果为48
原因解释:对于子类派生于父类的情况,子类内存分配时要考虑父类的成员变量,具体原则为:先父类后子类,先前再后。如上例所示,内存一次分配8个字节,系统首先给父类的char b[5]分配8个字节,空闲的3个字节无法放下下一个成员变量double c,故又分配8个字节,接下来给子类的int a[3]分配了16个字节,空闲了4个字节,可以放下下一个成员变量Name[3],故不再开辟新的内存,最后给double b分配8个字节空间,共40个字节。注意:和子类是public/Private/Protected继承没有关系。
6. 派生类多重继承的情况
如果一个类多重继承于其它的类,则其内存占用大小等于各个父类内存占用大小之和加上子类内存占用的大小。其中,基准为所有类(包括子类和多个父类)中原生类型,如int,double等,占用内存最大的那个。
总结:
静态成员变量是不占用堆栈内存的,因为系统将其放在了全局存储区。
普通成员函数是不占用堆栈内存的,访问靠this指针。
内存分配的基准需要将子类和父类所有的普通成员变量一块儿考虑。
虚函数需要一个Vptr和普通成员变量内存分配是分开的,但内存分配的基准是一样的。