我们知道,为了提高内存寻址的效率,很多处理器体系结构为特定的数据类型引入
了特殊的内存对齐需求。不同的系统和编译器,内存对齐的方式有所不同,为了满足
处理器的对齐要求,可能会在较小的成员后加入补位,从而导致结构体实际所占内存的
字节数比我们想象的多出一些字节。
下面从4个方面进行阐述: (1)结构体只包含基本数据类型;
(2)结构体含有复合数据类型(例如包含strcut,union);
(3)空结构体;
(4)结构体含位域。
下面的例子均在VS平台测试,不同平台实现可能有差异。
规律总结(按字节编址):1.从0位置开始存储;
2.short类型数据要求从偶数地址开始存储;
3.int数据要求对齐在4字节地址边界;
4.变量存储起始位置是该变量大小的整数倍;
5.结构体总的大小是其最大元素的整数倍,不足的后面补齐;
6.结构体变量的首地址能够被其最宽基本数据类型成员的大小整除;
例子1:
struct Test
{
int num;//0 1 2 3 占4个字节
char *pname;//4 5 6 7 占4个字节
short date;//8 9 占两个字节
char ch[2];//10 11 占两个字节
short sh[4];//12 13 14 15 16 17 18 19 占8个字节 满足上面的规律5
};
4+4+2*2+8=20 满足上面的5条规律 故sizeof(struct Test)=20
例子2:
struct Test
{
char c;//0 1 2 3 占4个字节(补3个字节,因为int占4个字节,需满足规律4)
int a;// 4 5 6 7 占4个字节
short s;//8 9 10 11 占4个字节(补两个字节,因为int占4个字节,需满足规律5)
};
4*3=12 故sizeof(struct Test)=12
例子3:
struct Test
{
int a;//0 1 2 3 占4个字节
char b;//4 5 占两个字节(补1个字节,因为short占两个字节,需满足规律4)
short c;//6 7 占两个字节
};
4+2+2=8 故sizeof(struct Test)=8
规律:1.在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,
而不是把复合成员看成是一个整体;
2.在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
例子1:
struct s1
{
char c;//0 1 2 3 占4个字节
int b;//4 5 6 7 占4个字节
};
//s1结构体共占8个字节
struct s2
{
char c1;//0 1 2 3 占4个字节
struct s1 s;//4-11 占8个字节
char c2;//12-15 占4个字节
};
故sizeof(struct s2)=16个字节
说明:s1最宽简单成员的类型为int,s2在考虑最宽简单类型成员时是将s1“打散”看的,所以s2的最宽基本类型为int,这样,通过s1定义的变量,其存储空间的首地址应该被4整除(上面的规律6),整个sizeof(s2)的值也应该被4整除(规律5)。c1的偏移量为0,s的偏移量呢,这时s是一个整体,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。
例子2:
union u1
{
char bj[5];
int bh;
};
//共用体类型所占内存空间的大小取决于其成员中占内存空间最多的那个成员变量。
//故为字符数组这个成员变量,同样,整个共用体所占的内存字节数应为最宽数据类型的整数倍,
//需在后面补3个字节,即:sizeof(union u1)=8。
struct s
{
union u1
{
char bj[5];
int bh;
};
char c[8];
float f;
};
说明:整个共用体共占8字节内存空间,即:0-7,字符数组占8个字节内存空间,即:8-15,float类型占4个字节内存空间,即:16-19。故,sizeof(struct s)=20。
例子3:
union u1
{
char bj[5];
int bh;
};
struct s
{
union u1
{
char bj[5];
int bh;
};
char c[7];
};
说明:整个共用体共占8个字节内存空间,即:0-7,字符数组占7个字节内存空间,即8-14,这时,我们同样也需要将共用体“打散”看,结构体最宽基本数据类型为int,故整个结构体所占字节数为16(需在最后补1个字节,满足规律5),即:sizeof(struct s)=16。
空结构体(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址,
两个不同的“空结构体”变量又如何得以区分呢?于是,“空结构体”变量也得被存储,
这样编译器也就只能为其分配一个字节的空间用于占位了。
C99规定,int、unsigned int和bool可以作为位域类型,但编译器几乎都对此做了拓展,允许
其他类型的存在。使用位域的主要目的是压缩存储。
规律:
1.如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前
一个字段存储,直到不能容纳为止;
2.如果相邻位于字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的
存储单元开始,其偏移量为其类型大小的整数倍;
3.如果相邻的位域字段的类型不同,其各编译器的具体实现有差异,VS采取不压缩方式;
4.如果位域字段之间穿插着非位域字段,则不进行压缩;
5.整个结构体的总大小为最宽基本类型成员大小的整数倍。
例子1:
struct s1
{
char c1:3;
char c2:4;
char c3:5;
};
其内存布局:
| —c1---- | ----c2------ | _ |-------c3---------|------------|
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |
位域类型为char,第1个字节仅能容下c1和c2,所以c2被压缩到第1个字节中(规律1),而c3只能从下一个字节开始(规律2)。所以sizeof(struct s1)=2。
例子2:
strcut s2
{
char c1:3;
short s1:4;
char c2:5;
};
由于相邻位域类型不同,在VS中不采取压缩方式,故c1占1个字节,由于s1为short类型,故c1补一个字节。s1占两个字节,c2占一个字节,再补一个字节。故sizeof(struct s2)=6。
例子3:
struct s3
{
char c1:3;
char c2;
char c3:5;
};
由于非位域字段穿插在其中,不会产生压缩。故sizeof(struct s3)=3。