内存对齐指的是结构体中对结构成员内存的一系列调整。通过调整offset位置,减少读取结构成员数据需要的CPU-> 内存读取次数。任何对象数据在底层实际上是结构体。
对齐规则
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
是不是有一点眼晕?我自己总结的计算方法如下:
内存对齐原则 (简化版):
- 找到最大的基础类型成员 , 将其内存长度作为基准
- 每个成员的offset,需要是成员自己类型的整数倍
- 放完之后,struct自身的数据需要为基准的整数倍
让我们看一个简单的数据:
struct Struct1 {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
}struct1;
1. 最长的元素是double,基准为8
2.
struct Struct1 {
double a; // 长度为8 offset:0,0是8的整数倍,所以 a存放位置为 (0 - 7)
char b; // 长度为1 offset: 8 8是 1的整数倍,所以 b存放位置为 (8)
int c; // 长度为4 offset: 9 9不是4的整数倍,offset后移到 12 ,所以 c存放位置为 (12 - 15)
short d; // 长度为2 offset: 16 16是2的整数倍,所以 d存放位置为 (16 - 17)
}struct1;
3. 最终数据成员的整体内存占用位置为 (0 - 17),18个字节。18不是基准 8 的整数倍,因此填充到24.
最终 sizeof(struct1) 的大小为 24
如果我们稍微调整下数据顺序:
struct Struct2 {
double a; // 8
// 这里调整了 b,c的顺序
int b; // 4
char c; // 1
short d; // 2
}struct2;
1. 最长的元素是double,基准为8
2.
struct Struct2 {
double a; // 长度为8 offset:0,0是8的整数倍,所以 a存放位置为 (0 - 7)
int b; // 长度为4 offset: 8 8是 4的整数倍,所以 b存放位置为 (8 - 11)
char c; // 长度为1 offset:12 12是1的整数倍,所以 c存放位置为 (12)
short d; // 长度为2 offset: 13 13不是2的整数倍,offset后移到 14 所以 d存放位置为 (14 - 15)
}struct2;
3. 最终数据成员的整体内存占用位置为 (0 - 15),16个字节。16是基准 8 的整数倍,因此不需要填充。
最终 sizeof(struct2) 的大小为 16
成员为数组的情况:
内存对齐原则 ( ex1):
- 找到最大的基础类型成员 , 将其内存长度作为基准 (如果成员为数组,基准不按数组总长,而是按照数组元素类型来计算)
struct Struct3 {
char a; // 1 (0)
int b[2]; // b0 b1
double c; //8
}struct3;
1. 最长的元素是double,基准为8
2.
struct Struct3 {
char a; // 长度为1 offset:0 0是1的整数倍,所以 a存放位置为 (0)
int b[2]; // b0 长度为4 0ffset:1 1不是4的整数倍,b0的offest后移到4,所以 b0存放位置为 (4 - 7)b1存放位置为 (8 - 11)
double c; //长度为8 offset: 12 12不是8的整数倍,offset后移到 16 所以 d存放位置为 (16 - 23)
}struct3;
3. 最终数据成员的整体内存占用位置为 (0 - 23),24个字节。24是基准 8 的整数倍,因此不需要填充。
最终 sizeof(struct3) 的大小为 24
其实数组写法很类似下面的写法:
struct Struct3 {
char a;
int b0;
int b1;
double c;
}struct3;
只要调整对了数组第一个元素的offset,数组后续元素自然可以对齐。
成员为struct的情况:
- 找到最大的基础类型成员 , 将其内存长度作为基准 (如果成员为struct,基准不按子struct总长,而是max(子struct基准,剩余成员内存长度))
- 每个成员的offset,需要是成员自己类型的整数倍 (如果成员为struct, 只需是 成员struct 基准的整数倍而不是总长的整数倍)
struct Struct1 {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
}struct1;
struct Struct4 {
int b; // 4 // (0-3)
char c; // 1 // (4)
struct Struct1 s1; // a:8 (8 - 15) b:1 (16) c:4 (20 - 23) d:2 (24-25) (8 - 25) 18字节 ->
short d; // 2 // 38 (36 - 37)
}struct4;
1. Struct1的基准为8,除Struct1 s1外,Struct4最长的元素是int 长为4,基准为max(8,4) = 8
2.
struct Struct4 {
int b; // 4 // offset:0 ,存放位置: (0-3)
char c; // 1 // offset:4,存放位置: (4)
struct Struct1 s1;
/** a: 长度: 8 offset:5 -> 8 存放位置: (8 - 15)
b:长度: 1 offset:16 存放位置:(16)
c: 长度: 4 offset:17 -> 20 存放位置: (20 - 23)
d: 长度: 2 offset:24 存放位置: (24-25)
s1 总体位置:(8 - 25) 18字节 -> 填充 (8 - 31)24字节
**/
short d; // 2 // offset:32,存放位置: (32 - 33)
}struct4;
3. 总体位置 (0 - 33) 长度为 34,不是8的倍数,填充到 40
最终 sizeof(struct4) 的大小为 40
附表:
-
基础数据表
2.内存分布查看方法:
po &struct4 -> 0x00000001014cc600
x/8gx 0x00000001014cc600