目录
结构体内存对齐规则
我们知道不同的变量、数组都有自己的大小,结构体也有自己的大小,但是结构体的大小并不是简单地将每个结构体成员的大小相加就能得到。
计算结构体内存大小遵循的结构体内存对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。
注意:vs编译器中的默认对齐数是8,有些编译器没有设置默认对齐数,对齐数就是该成员变量自身的对齐数。
计算结构体大小的三个步骤
知道了结构体的内存对齐规则,我们可以通过下面三个步骤计算结构体的大小了。
拿下面这个结构体来举例:
struct s
{
char a;
int b;
double c;
};
第一步:找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数。
第二步:根据每个成员对应的对齐数画出它们在内存中的相对位置。
3.通过最大对齐数决定整个结构体的大小
该结构体的最大对齐数(所有成员对齐数中的最大值)为8,因此结构体的大小为8的倍数,即结构体的大小为:绿色部分+黄色部分+红色部分+空白部分=8*n;所以该结构体的大小为16。
注意:大多数情况下,成员变量已经占用的总字节个数并不一定正好为其成员变量中的最大对齐数的整数倍,这时我们需要将其扩大为最大对齐数的整数倍。
为什么需要设置内存对齐规则?
平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。
比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。
性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次,是以牺牲内存换性能的方法(空间换时间)。
设计结构体时的技巧
在画结构体成员变量在内存中相对位置时,我们可以发现,结构体成员的顺序也会影响结构体的内存大小,两个结构体的成员变量相同,但是成员变量的顺序不同,可能就会出现结构体的大小不同的情况:
struct s1//结构体s1大小为8
{
char a;
char b;
int c;
};
struct s2//结构体s2大小为12
{
char a;
int c;
char b;
};
因此通过合理设计结构体成员顺序,尽量将占用内存空间小的成员放置在一起就可能避免对内存空间的不必要消耗。
修改编译器的默认对齐数
要修改编译器的默认对齐数,需要用到以下预处理命令。
#pragma pack()
括号内可以数字,表示你需要将编译器默认对齐数设置为多少,括号内不填数字表示恢复编译器默认对齐数。
#include<iostream>
using namespace std;
//编译器默认对齐数为8
#pragma pack(2)//默认对齐数设置为4
struct s1//结构体s1大小为6
{
char a;
char b;
int c;
};
#pragma pack()
struct s2//结构体s2大小为8
{
char a;
char b;
int c;
};
int main()
{
cout << sizeof(s1) << endl;
cout << sizeof(s2) << endl;
return 0;
}
当有需要的时候,我们可以使用整个方法来更改编译器的默认对齐数。