字节对齐原因:
1)平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2)硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升,帮助cpu寻址。
【注意】(对齐位数跟处理器位数和编译器都有关)VS, VC等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。
改变缺省的对界条件(指定对界):
- 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
- 使用伪指令#pragma pack(),取消自定义字节对齐方式。
对齐规则
规则一:结构体的每个成员相对于结构体的首地址的偏移量,都是基本成员大小的整数倍。比如
struct{ int a; //4 首地址&a char b; //1 第二个成员&b相对于第一个成员&a是整数倍 double c; //8 }
规则二: 如果有嵌套结构体或者有联合类型,那么内嵌结构体的第一个成员变量在外结构体中的偏移量,是内嵌结构体或联合体中那个数据类型大小最大的成员变量的倍数。
规则三: 整个结构体的大小要是这个结构体内数据类型大小最大的成员变量的倍数。如果有内嵌结构体,那么取内嵌结构体中数据类型大小最大的成员变量作为计算外结构体整体大小的依据。
最终有效的字节对齐值:
在自身字节对齐值和编译器要求的字节对齐值中取较小的。
比如:Linux 64位默认对齐为8位,下面输出为16,long在64位下为8位
typedef struct Test { short a; long e; } t1; int main() { printf("%d ", sizeof(t1)); return 0; }
但是在前面加了 #pragma pack(4)
#pragma pack(4) typedef struct Test { short a; long e; } t1;
输出将变为12
结构体成员定义顺序不同,结构体大小也不同。
实例说明
st1占8字节,st2占12字节
str3占16字节,str4占12字节
st5占用16字节,st6占用24字节
嵌套的结构体例子
typedef struct TEST{ int na; char cb; char cc; int nd; char cf; struct TT{ int ng; long long llh; }tt; char ci; }test;
内存分布图
如上图所示,根据规则一,nd在结构体内的位置必须满足是其自身数据类型大小的整数倍,且倍数要取满足条件的最小倍数。因为nd前三个成员变量总大小是6Bytes,nd作为int类型,其数据类型大小是4Bytes,满足条件的最小倍数是2,所以nd在结构体中的偏移量是8,故填充2Bytes。
根据规则二,ng作为内嵌结构体的第一个成员变量,它在外结构体中的偏移量要满足——其内嵌结构体中最大数据类型大小的倍数。在计算cf偏移量后,当前偏移量是13,而内嵌结构体中最大数据类型为long long,其大小为8,ng的偏移量必须是8的倍数,且取大于13的最小倍数,即2。所以ng前要填充3Bytes。
根据规则三,计算完ci后,当前偏移量是33。如果要满足test结构体的大小是其中最大数据成员类型大小的倍数,在此例中是内嵌结构体的成员变量llh,即8的倍数。所以要填充7Bytes。
空结构体
空结构体在C下为0,在C++下为1
在C++下,空类和空结构体的大小是1(编译器相关),这是为什么呢?为什么不是0?
这是因为,C++标准中规定,“no object shall have the same address in memory as any other variable” ,就是任何不同的对象不能拥有相同的内存地址。 如果空类大小为0,若我们声明一个这个类的对象数组,那么数组中的每个对象都拥有了相同的地址,这显然是违背标准的。
C++标准规定不同的对象不能拥有相同的地址。那么怎样才能保证这个条件被满足呢?最简单的方法莫过于不允许任何类型的大小为0。所以编译器为每个空类或者空结构体都增加了一个虚设的字节(有的编译器可能加的更多),这样这些空类和空结构的大小就不会是0,就可以保证他们的对象拥有彼此独立的地址。
字节对齐的一些陷阱
如果在结构体中嵌套了结构体,要先把中间的结构体算出来
1.如果嵌套的结构体中没有变量名,就表示整个嵌套的结构体只是一个类型,它就不会占用空间
#pragma pack(4) typedef struct Test { short a; struct t //如果没有t,成员的变量空间都要计算 { int b; double c; char d; }; //没有变量名,不占用空间 long e; }; //输出结果:12
2.如果结构体中没有变量名,也没有结构体名,那么结构体中的所有成员空间都要计算在内,并且结构体中的成员都要对齐
#pragma pack(4) typedef struct Test { short a; struct //如果没有结构体名 { int b; double c; char d; };//有变量名或没有变量名 long e; }t1; //输出结果:28
结构体定义的建议
相同类型的变量最好放在一起,会减小结构体的空间
变量类型从小到大的顺序比较合适