0%

CPP-内存对齐

这篇文章记述了关于内存对齐的知识点。

阿里:struct 和 union 的区别

1、struct中每个成员都会分配到内存位置,而union则是大家共用一块内存位置(你用完了我用,所以会存在数据覆盖的问题);

2、struct的大小为几个成员变量所占的大小之和(还得考虑内存对齐哈),而union的大小则取决于内部元素中类型最大的那一个;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
union u
{
 double a;
 int b;
};

union u2
{
 char a[13];
 int b;
};

union u3
{
 char a[13];
 char b;
};

cout<<sizeof(u)<<endl; // 8 double为8位嘛!
cout<<sizeof(u2)<<endl; // 16 本来应该是13,但是考虑四字节对齐,最接近的就是16了!
cout<<sizeof(u3)<<endl; // 13 为什么这个就是13呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。

可以采用#pragma pack(X)来修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma pack(2)
union u2
{
 char a[13];
 int b;
};
union u3
{
 char a[13];
 char b;
};
#pragma pack(8)

cout<<sizeof(u2)<<endl; // 14 由于手动更改对界方式为2,所以int的对界也变成了2,u2的对界取成员中最大的对界,也是2了,所以此时sizeof(u2)=14。
cout<<sizeof(u3)<<endl; // 13 ,char的对界为1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
structsizeof问题
struct s1
{
 char a;
 double b;
 int c;
 char d;
};

struct s2
{
 char a;
 char b;
 int c;
 double d;
};

cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 16

同样是两个char类型,一个int类型,一个double类型,但是因为对界问题,导致他们的大小不同。计算结构体大小可以采用元素摆放法,我举例子说明一下:首先,CPU判断结构体的对界,根据上一节的结论,s1和s2的对界都取最大的元素类型,也就是double类型的对界8。然后开始摆放每个元素。

  对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址是8了,所以d被放在了8,此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被保留,s1的大小变成了24。

  对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,结构体在15处结束,占用总空间为16,正好是8的倍数。

总结:当结构体当中出现double,int等元素时,要进行内存对齐,分别是8 和 4

什么是内存对齐

从理论上来讲,我们可以从任一字节位置处开始读取,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//32位系统
#include<stdio.h>
struct
{
int i;
char c1;
char c2;
}x1;

struct{
char c1;
int i;
char c2;
}x2;

struct{
char c1;
char c2;
int i;
}x3;

int main()
{
printf("%d\n",sizeof(x1)); // 输出8
printf("%d\n",sizeof(x2)); // 输出12
printf("%d\n",sizeof(x3)); // 输出8
return 0;
}

image-20201111103623835

如果前面加上#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下:

image-20201111103659418

Welcome to my other publishing channels