目录
一,内存对齐的三条规则
- 数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
- 结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
- 结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐
规则描述比较抽象,详情请看下面的举例说明。
二,注意事项:
- 数组在内存中存储时是分开存储的,char类型的数组每个元素是 1Byte,内存对齐时按照单个元素进行对齐
- union(联合体)类型中的数据共用内存,联合的所有成员共用一段内存空间,存储地址的起始位置都相同,一般来说最大成员的内存宽度作为union的内存大小,主要的原因是为了节省内存空间,默认的访问权限是公有的,但是它同样要遵守内存对齐的原则,特别是第3条规则
- C++中空结构体占用 1Byte
- C++中空类同样是占用 1Byte的内存空间
- 类和结构体一样,需要内存对齐
三,举例说明
1,示例1
struct Test1 {
int a;
double b;
char c;
};//24
类型 | int | double | char |
地址 | 0~3 | 8~15 | 16 |
累计所占内存(字节) | 4 | 16 | 17 |
按照规则(最大成员长度整数倍) | 4 | 16 | 24 |
解释:
- 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,示例2
struct Test2 {
int a;
double b;
char c[6];
};//24
类型 | int | double | char [6] |
地址 | 0~3 | 8~15 | 16~21 |
累计所占内存(字节) | 4 | 16 | 21 |
按照规则(最大成员长度整数倍) | 4 | 16 | 24 |
解释:
- int a; 占用 4Byte(存储位置0-3),规则1
- double b; 占用 8Byte(存储位置是从该类型长度(也就是 8Byte)或整数倍开始存储8-15),规则1
- 数组在内存中存储时是分开存储的,char类型的数组每个元素是 1Byte,按单个元素进行内存对齐,故sizeof大小还是24,注意1 & 规则3。所以下面的Test22的大小也是24 bytes。
struct Test22 {
int a;
double b;
char c[5];
};//24
3,示例3
struct Test {
int a;
double b;
char c;
};
struct Test3 {
int a;
Test d;
double b;
char c;
};//48
类型 | int | int | double | char | double | char |
地址 | 0~3 | 8~11 | 16~23 | 24 | 32~39 | 40 |
累计所占内存(字节) | 4 | 12 | 24 | 25 | 40 | 41 |
按照规则(最大成员长度整数倍) | 48 |
解释:
- 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),40是最大元素大小8的整数倍,但是要大于40,按照规则3补齐,sizeof为48,规则1 & 规则2 & 规则3
4,示例4
struct Test {
int a;
double b;
char c;
};
struct Test3 {
int a;
Test d;
char c;
};//40
类型 | int | int | double | char | char |
地址 | 0~3 | 8~11 | 16~23 | 24 | 32 |
累计所占内存(字节) | 4 | 12 | 24 | 25 | 33 |
按照规则(最大成员长度整数倍) | 24 | 32 | 40 |
Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照成员结构体内部的最大成员的整数倍进行补齐的,sizeof的结果是40
四,联合体(union)
找到联合体中最大的数据类型,还必须满足是所有成员的整数倍
union Test{
char a[20];
int b;
float c;
};
sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3
union Test{
char a[20];
int b;
float c;
double d;
};
sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3
五,关于数组的内存对齐问题
关于数组的内存对齐问题,实际上在内存眼中,数组是若干个连续的相同大小元素的集合。也就是说char[20] 和 连续的20 个char 元素在内存中没有什么分别。所以当结构体中有数组时,可以把它拆解开成连续排列的多个子元素。如下代码示例:
struct str1{
double a;
char no[9];
int bb;
double aa;
} s1;
struct str2{
double a;
char no1;
char no2;
char no3;
char no4;
char no5;
char no6;
char no7;
char no8;
char no9;
int bb;
double aa;
} s2;
// sizeof(s1) = sizeof(s2) = 32
六,字节对齐的原因
- 平台原因(移植原因),不是所有的硬件平台都能任意访问地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
- 性能原因,经过内存对齐后,CPU的访问效率会得到很大的提高(CPU把内存当成是一块一块的,块的大小可以是2,4,8,16Byte 大小,因此CPU在读取内存时是一块一块进行读取的,当读取块的大小是 4Byte 时,一个数据所占的字节偏移(offset)为3|4|5|6,那么CPU访问数据时便需要访问两次,才能得到完整的数据,经过内存对齐后,便可以通过一次访问CPU获取完整的数据
七,计算练习
struct s1{
char ch1;
char ch2;
int i;
};//8
struct s2{
char ch1;
int i;
char ch2;
};//12
struct s3{
char ch;
int i;
char str[10];
};//20
struct s4{
char ch;
int i;
struct s{
char ch1;
int j;
}sub;
float f;
};//20
struct s5{
char ch;
char i;
struct s{
char ch1;
double j;
}sub;
char f;
};//32
struct A
{
union B
{
char* pc;
int z;
};
}; //1, struct A is empty struct
struct AA
{
union
{
char* pc;
int z;
};
};//32-bit: 4, 64-bit: 8
struct AAA
{
union
{
char* pc;
int z;
} B;
};//32-bit: 4, 64-bit: 8
有同学问关于练习题中s5为什么是32 byte,而不是24 byte,下面作如下解答:
struct s5{
char ch;
char i;
struct s{
char ch1;
double j;
}sub;
char f;
};//32
类型 | char | char | char | double | char |
地址 | 0 | 1 | 8 | 16~23 | 24 |
累计所占内存(字节) | 1 | 2 | 16 | 24 | 25 |
按照规则(最大成员长度整数倍) | 32 |
解释:
- char ch; 占用 1 Byte(存储位置0),规则1
- char i; 占用 1 Byte(存储位置1),规则1
- s 中最大的元素是double j; 占用 8Byte,s中的成员是按照 8Byte 的整数倍的地址开始存储的,s中char ch1; 占用 1Byte(存储位置8),double j; 占用 8Byte(存储位置16-23);
- char f; 占用1 Byte(存储位置24)
- 累积大小为25,按照规则3补齐,sizeof为32,规则1 & 规则2 & 规则3
同学可以尝试将char f 注释,最后将得到24,也可以从侧面说明再加上一个char f,其大小肯定大于等于24 byte。
struct s5{
char ch;
char i;
struct s{
char ch1;
double j;
}sub;
// char f;
};//24
新增两个小实验以回答评论区同学的问题:
struct s5{
char ch;
char i;
char f[6];
struct s{
char ch1;
double j;
}sub;
// char f;
};//24
struct s5{
char ch;
char i;
char f[7];
struct s{
char ch1;
double j;
}sub;
// char f;
};//32