结构体内存对齐的规则
- 第一个成员在结构体对象的首地址处。
- 其他成员变量要对齐到对齐数的整数倍。
- 结构体对象的总大小是最大对齐数的整数倍。
- 如果结构体内嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处。结构体整个大小就是最大对齐数的整数倍。
对齐数:该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值就是对齐数。
vs系列的编译器默认对齐数是8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。
struct S
{
double d;
char c;
int i;
};
比如上面结构体S,在vs编译器下,默认对齐数是8。
- d成员的大小是8,默认对齐数是8,较小值是8。最终对齐数就是8。
- c成员的大小是1,默认对齐数是8,较小值是1。最终对齐数就是1。
- i成员的大小是4,默认对齐数是8,较小值是4。最终对齐数就是4。
在整个结构体对象内存中。
d是第一个成员,对齐到结构体对象的首地址即可。c要对齐到1的整数倍,i要对齐到4的整数倍。
图解:
-
d从起始地址占8个字节即可,c对齐1的整数倍,占1个字节即可。i要对齐到4的整数倍,占4个字节。
-
然后确定整个结构体对象的总大小。最大对齐数的整数倍,也就是8的整数倍。[0,15]一共占用了16个字节,是8的整数倍。所以整个结构体对象S的大小就是16字节。
-
在vs下创建一个S对象,一共要用占用16字节,浪费了3字节。
当有嵌套的时候。
struct S
{
double d;
char c;
int i;
};
struct SS
{
struct S a;
char b;
}
- SS的成员a大小是16,默认对齐数是8,较小值是8,最终对齐数是8。
- 成员b的大小是1,默认对齐数是1,较小值是1。最终对齐数是1。
a是第一个成员大小,占16个字节即可。b对齐到1的整数倍,也就是16。最终整个SS对象大小是8的整数倍,[0,16]是17个字节,所以SS对象大小是24字节。
为什么存在内存对齐?
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。
比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。 - 性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次。
结构体设计的技巧
在我们设计结构体的时候,如果结构体成员的顺序设计得合理的话,是可以避免不必要的内存消耗的。
两个结构体的成员变量相同,但是成员变量的顺序不同,可能就会出现结构体的大小不同的情况:
struct S1
{
char a;
char b;
int c;
};//结构体1
struct S2
{
char a;
int c;
char b;
};//结构体2
两个同样的结构体1和2,由于成员变量的顺序不一样,结构体S1的大小是8,s2的大小是12。
可以见得,结构体成员变量的顺序不同,可能会造成内存不必要的损失。将占用空间小的成员尽量集中在一起,可以有效地避免内存不必要的浪费。
修改默认对齐数
要修改编译器的默认对齐数,我们需要借助于以下预处理命令:
#pragma pack(4)//设置默认对齐数为4
#pragma pack()//取消设置的默认对齐数,还原为默认