想必大部分码农在平时嵌入式应用开发的时候很少考虑结构体的内存对齐以及大小问题,现在在空余时间重新温习一下C语言基础,搜集一些以前遗忘的知识点记录下来。
在考虑内存对齐之前先回顾一下各种数据类型占用的字节数。
32位系统下基于Microsoft、Borland和GNU的编译器,有如下数据对齐规则:
a、一个char(占用1-byte)变量以1-byte对齐。
b、一个short(占用2-byte)变量以2-byte对齐。
c、一个int(占用4-byte)变量以4-byte对齐。
d、一个long(占用4-byte)变量以4-byte对齐。
e、一个float(占用4-byte)变量以4-byte对齐。
f、一个double(占用8-byte)变量以8-byte对齐。
g、一个long double(占用12-byte)变量以4-byte对齐。
h、任何pointer(占用4-byte)变量以4-byte对齐。
而在64位系统下,与上面规则对比有如下不同:
a、一个long(占用8-byte)变量以8-byte对齐。
b、一个double(占用8-byte)变量以8-byte对齐。
c、一个long double(占用16-byte)变量以16-byte对齐。
d、任何pointer(占用8-byte)变量以8-byte对齐。
结构体在内存对齐上遵循下面原则:
原则1:数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要
根据#pragma pack设置的对齐方式进行对齐,这里存在两种情况:如果有成员的字节数大于设置值或者小于,这里遵循取小原则。
比如:
struct test{
char a;
double b;
int c;
};
原则2:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从double的整数倍开始存储。)
原则3:结构体的总大小,必须是其内部最大成员的整数倍,不足的要补齐。还有另外一种情况,结构体内有位字段。基本规则如下:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
例:
struct A{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};
位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。
例:
struct B{
char f1 : 3;
short f2 : 4;
char f3 : 5;
};
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
struct C{
char f1 : 3;
char f2;
char f3 : 5;
};
在VC6和Dev-C++中得到的大小均为3。
struct B{
char f1 : 3;
short f2 : 4;
char f3 : 5;
};
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
如果再有一种情况是包含union,只要弄清union的保存方式(占用字节最大的类型为联合的大小)即可。