C/C++ 中内存对齐--结构体
内存对齐指令
-
一般来说,内存对齐过程对 coding 者来说是透明的,是由编译器控制完成的
-
如对内存对齐有明确要求,可用 #pragma pack(n) 指定,以 n 和结构体中最长数据成员长度中较小者为有效值
-
如未明确指定时,以结构体中最长的数据成员长度作为内存对齐的有效值
以下如没有特殊说明,均视为情况 3 (未明确指定)计算
内存对齐的三条规则
-
数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在 offset 为 0 的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从 4 的整数倍地址开始存储);
-
结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储;
-
结构体的总大小,即 sizeof 的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐;
另外需要注意的几点:
-
数组在内存中存储时是分开存储的,char 类型的数组每个元素是 1Byte,内存对齐时按照单个元素进行对齐;
-
union(联合体)类型中的数据共用内存,联合的所有成员共用一段内存空间,存储地址的起始位置都相同,一般来说最大成员的内存宽度作为 union 的内存大小,主要的原因是为了节省内存空间,默认的访问权限是公有的,但是它同样要遵守内存对齐的原则,特别是第 3 条规则;
-
C++ 中空结构体占用 1Byte;
-
C++ 中空类同样是占用 1Byte的内存空间(剑指offer 2.2.1节中中提到,当声明该类型的实例的时候,必须在内存中占有一定的空间,否则无法使用这些实例,占用多少内存由编译器决定);
举例说明
例1
struct Test1 {
int a; // 4 -> 8
double b; // 8 -> 8
char c; // 1 -> 8
};
说明
-
int a; 占用 4Byte(存储位置0-3),规则1;
-
double b; 占用 8Byte(存储位置是从该类型长度(也就是 8Byte)或整数倍开始存储8-15),规则1;
-
char c; 占用 1Byte(存储位置16),规则1;
-
这时一共用了 17 Byte,但是 sizeof 所得的大小为 24,这就用到了第 3 条规则,最后 sizeof 的大小还必须是内部最大成员长度的整数倍,不足的要补齐,这个结构体中最大成员是 double b; 8 Byte,最后sizeof的大小为 24,规则3;
例2
struct Test2 {
int a; // 4 -> 8
double b; // 8 -> 8
char c[6]; // 6 -> 8
};
说明
- int a; 占用 4Byte(存储位置0-3),规则1;
- double b; 占用 8Byte(存储位置是从该类型长度(也就是 8Byte)或整数倍开始存储8-15),规则1;
- 数组在内存中存储时是分开存储的,char 类型的数组每个元素是 1Byte,按单个元素进行内存对齐,故 sizeof大小 还是 24,注意1 & 规则3;
例3
struct Test {
int a; // 4 -> 8
double b; // 8 -> 8
char c; // 1 -> 8
};
struct Test3 {
int a; // 4 -> 8
Test d; // 24 -> 24
double b; // 8 -> 8
char c; // 1 -> 8
};
说明
- int a; 占用 4Byte(存储位置0-3),规则1;
- Test 中最大的元素是 double b; 占用 8Byte,Test 中的成员是按照 8Byte 的整数倍的地址开始存储的,Test 中 int a; 占用 4Byte(存储位置8-11),double b; 占用 8Byte(存储位置16-23),char c; 占用 1Byte(存储位置 24),规则2;
- double b; 占用 8Byte(存储位置32-39),规则1;
- char c; 占用 1 Byte(存储位置40),不是最大元素大小 8 的整数倍,按照规则3补齐,sizeof 为 48,规则1 & 规则2 & 规则3;
例4
struct Test {
int a; // 4 -> 8
double b; // 8 -> 8
char c; // 1 -> 8
};
struct Test3 {
int a; // 4 -> 8
Test d; // 24 -> 24
char c; // 1 -> 8
};
说明
- Test3 中的最大数据成员大小比成员结构体 Test 内部最大成员大小要小,这时规则3是按照成员结构体内部的最大成员的整数倍进行补齐的,sizeof 的结果是 40
例5
union Test{
char a[20]; // 1 -> 20
int b; // 4
float c; // 4
};
说明
- sizeof 的大小是 20,即 $a[20]$ 的大小,同样 20 是 b 和 c 的倍数,规则3
例6
union Test{
char a[20]; // 1 -> 20
int b; // 4
float c; // 4
double d; // 8
};
说明
- sizeof 的大小是 24,即满足容下 $a[20]$,同样 24 是b、c和d的倍数,规则3
字节对齐的原因:
-
平台原因(移植原因),不是所有的硬件平台都能任意访问地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常;
-
性能原因,经过内存对齐后,CPU 的访问效率会得到很大的提高( CPU 把内存当成是一块一块的,块的大小可以是 2,4,8,16Byte 大小,因此 CPU 在读取内存时是一块一块进行读取的,当读取块的大小是 4Byte 时,一个数据所占的字节偏移(offset)为3 4 5 6,那么 CPU 访问数据时便需要访问两次,才能得到完整的数据,经过内存对齐后,便可以通过一次访问CPU获取完整的数据)