说明:以32位系统为例,以class举例说明(C++的struct和class基本是一样的)内存分配的问题。
1、空类
class A{
};
sizeof(A) = 1
C++标准规定,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址。这是由于:
- new需要分配不同的内存地址,不能分配内存大小为0的空间
- 避免除以 sizeof(T)时得到除以0错误
故使用一个字节来区分空类。
延伸的问题:
class A{
};
//(1)继承空类的类
class B : A{
int a;
};
//sizeof(B)=4: 子类继承空类后,子类如果有自己的数据成员,而空基类的一个字节并不会加到子类中去。
//(2)包含空类的类
class C{
int i;
A a;
};
//sizeof(C)=8: 内存对齐,所以是8字节
class D{
A a;
};
//sizeof(D)=1: D中只有一个成员变量A,所以是1字节;只有一个变量时不用考虑内存对齐
2、只包含成员变量的类/结构体——内存对齐规则
class A
{
int a;
double b;
short c;
};
class B
{
int a;
short b;
double c;
};
sizeof(A)=24而sizeof(B)=16为什么会产生不一样的结果呢? 这是非常简单的一个例子,体现了结构体/类的内存对齐规则。
在结构体中,从结构体的首地址开始,假设地址从0开始。
对结构体A来说,a占4个字节,占从0~3的字节,b是double类型占8个字节,占从8~15的字节,c占两个字节,从16~17的字节。
对结构体B来说,a占4个字节,从0~3,b占两个字节从4~6;c占8个字节从8~15。
这就是内存对齐,对齐规则是按照成员的声明顺序,依次安排内存,其偏移量为该成员大小的整数倍,0看做任何成员的整数倍,最后结构体的大小为最大成员的整数倍(所以这里的A的大小是24,而不是18)。
为什么要内存对齐?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
3、包含函数的类/结构体
包含普通函数:
普通函数不需要类为之分配内存。
包含虚函数:
只要类中有虚函数,那么该类就需要多出一份至少4字节的虚函数表地址(不论是有一个两个还是多个虚函数,都只需要一份!)
这个怎么理解呢?
首先,根据虚函数的底层实现原理,虚函数是由一个叫做虚函数表地址的内存单元指向类中的各个虚函数的地址的,所以不管类中有几个虚函数,都只需要分配一个虚函数表地址,而且这个虚函数表地址一般是放在类的起始地址!
其次,这个虚函数表地址分配多大呢?因为它是一个指针地址单元,所以32位系统下最少是4字节,但是——他的分配跟类中最大成员变量的内存大小有关!比如:
class A
{
int a;
virtual void fun();
virtual void fun2();
};
//sizeof(A) = 8
class A
{
int i;
double a;
virtual void fun();
virtual void fun2();
};
//sizeof(A) = 24
因为类的大小是最大成员变量的整数倍,所以当有成员变量大于4字节时,虚函数表地址单独分配一份相当于最大成员变量的内存。(虚函数表地址的分配不会与其他成员变量进行内存对齐!注意其特殊性!!)
4、有继承关系的内存分配
需要着重研究下有继承关系的时候,内存的分配原则,子类与父类分别包含有虚函数的时候的情况
5、虚继承的内存分配