结构体在内存中的存储
结构体对齐原因有很大部分是因为计算机扫描的内存单元个数,也就是数据总线的大小。
原则1:数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
原则2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
原则3:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。
例1:struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
sizeof(A) = 6; 这个很好理解,三个short都为2。
sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。
例2:struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};
sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。
sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。
深究一下,为什么是这样,我们可以看看内存里的布局情况。
a b c
A的内存布局:1111, 1*, 11
b a c
B的内存布局:1***, 1111, 11**
其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。
B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。
再看一个结构中含有结构成员的例子:
例3:struct A{
int a;
double b;
float c;
};
struct B{
char e[2];
int f;
double g;
short h;
struct A i;
};
sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。
sizeof(B) = 48; 看看B的内存布局。
e f g h i
B的内存布局:11* *, 1111, 11111111, 11 * * * * * *,1111* * * *, 11111111, 1111 * * * *
省略宏与结构体中含位域字段两种情况说明,祥见原文。
考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。
最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。