一.提出问题
首先来看看一个题目:
typedef struct
{
int a;
char b;
}A;
typedef struct
{
int a;
char b;
char c;
}B;
typedef struct
{
char a;
int b;
char c;
}C;
上面的三个结构体,哪一个占用内存空间最大呢?每个结构体各占用多少内存呢?
想到这里大家就会想到用sizeof操作符来一探究竟了,结果然我们大跌眼镜,占用内存竟然不一样。
下面是测试程序。
void main()
{
A a = { 'a' , 'b' , 1 } ;
B b = { 2 , 'c' , 'd' } ;
C c = { 'e' , 3 , 'f' } ;
cout<<sizeof(A)<<endl;//8
cout<<sizeof(B)<<endl;//8
cout<<sizeof(C)<<endl;//12
}
结果如下:
二.结果分析
大家可能会觉得结构体里面都是两个char一个int,应该占用内存为6个字节啊。有这个疑惑的童鞋可能不知道编译器对我们的程序做了什么,实际上这叫内存对齐。我们来调试程序,看看我们程序中的数据(三个结构体对象)在内存中是如何分布的,了解到了这一点,就不难知道为什么一个结构体对象变大了。
调试程序,得到三个对象的内存地址:
我们知道,因为这三个结构体对象时局部变量,所以会存储在栈中,栈是由高地址向低地址生长的,所以c的内存地址最小。来看看这三个对象的内存分布:
可以看到c占12个字节,中间的b占8个字节,最后的c占8个字节。为什么中间有填充呢?这就是我们之前说的内存对齐了。现在我们一个个来分析为什么是这样填充的。不过首先我们还是先看看内存对齐的规则:
1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
结合我们的额程序来分析分析:对于c:第一个成员为char,那么其存储位置为offset为0的地方,第二个成员为int,直接存储在char后即可,因为char后面的地址就满足了第一个条件(每个数据成员的地址都要是自身大小的倍数),故第一个char后面要填充三个字节才能存储下一个int型变量。接着存储char,char的存储地址满足自身大小的整数倍,那么第一个条件满足;但是因为第二个条件何第三个条件(结构体的大小必须为结构体成员中最大成员的整数倍,若不满足在最后一个成员的后面填充数据),所以在最后需要填充3个字节。
依次类推就很简单了。
三.实质性分析(为什么需要内存对齐)
需要对齐的原因有几点:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
内存对齐对于程序员来讲是透明的,但是要想干扰内存对齐那也可以。C语言给我们提供了方法。这里就不再展开,有兴趣的童鞋自己google一下。
(本文参考了博客:http://blog.csdn.net/yinkaizhong/article/details/4951288)