一、字节对齐的概念
如果一个变量占用N个字节,那么该变量的起始地址必须是N的整数倍。这就是字节对齐。当然,字节对齐是系统内部操作的。理论上来说,系统能从任意地址开始读取数据。但是有些系统偏偏喜欢规定某些类型变量只能从特定的起始地址开始访问,这就决定了类型变量必须按照某种规则在内存空间存放。
二、对齐准则
对齐细节与编译器有关,不过一般满足下面三个准则:
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
(2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
三、类的大小
下面先看一个例子。先看一个空的类占多少空间?
class Base
{
public:
Base();
~Base();
};
这时sizeof(Base)的结果是1.
任何类都要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
而析构函数、构造函数这些成员函数,是跟sizeof无关的,这也不难理解。因为我们的sizeof是针对实例,而普通成员函数是针对类体的,一个类的成员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小。
class Base
{
public:
Base();
virtual ~Base(); //每个实例都有虚函数表
void set_num(int num) //普通成员函数,为各实例公有,不归入sizeof统计
{
a=num;
}
private:
int a; //占4字节
char *p; //4字节指针
};
class Derive:public Base
{
public:
Derive():Base(){};
~Derive(){};
private:
static int st; //非实例独占、不归入sizeof统计
int d; //占4字节
char *p; //4字节指针
};
int main()
{
cout<<sizeof(Base)<<endl; // 12
cout<<sizeof(Derive)<<endl; // 20
return 0;
}
如果在Derive里加一个成员char c,这个时候,结果就变成了
12
24
可以归纳以下几个原则:
(1) 类的大小为类的非静态成员数据的类型大小之和,也就是说静态成员数据不作考虑。
(2) 普通成员函数与sizeof无关。
(3) 虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节。
(4) 类的总大小也遵守类似class字节对齐的,调整规则。
(5) 子类只是共用父类的虚函数表,因此一旦父类里有虚函数,子类的虚函数将不计入sizeof大小。
这可以认为是一个补充规则。