部分内容参考:https://www.jianshu.com/p/37409be16a37
部分内容参考:https://zhuanlan.zhihu.com/p/93822540
1. 内存简单原理
内存是由chip构成。每个chip内部,是由8个bank组成的。其构造如下图
每个bank内部,就是电容的行列矩阵结构了。(注意,二维矩阵中的一个元素一般存储着8个bit,也就是说包含了8个小电容)
8个同位置的元素,一起组成在内存中连续的64个bit。如下图
内存在进行IO的时候,一次操作取的就是64个bit。
所以,内存对齐最最底层的原因是内存的IO是以64bit为单位进行的。 对于64位数据宽度的内存,假如cpu也是64位的cpu(现在的计算机基本都是这样的),每次内存IO获取数据都是从同行同列的8个chip中各自读取一个字节拼起来的。从内存的0地址开始,0-63bit的数据可以一次IO读取出来,64-127bit的数据也可以一次读取出来。CPU和内存IO的硬件限制导致没办法一次跨在两个数据宽度中间进行IO。
假如对于一个c的程序员,如果把一个bigint(64位)地址写到的0x0001开始,而不是0x0000开始,那么数据并没有存在同一行列地址上。因此cpu必须得让内存工作两次才能取到完整的数据。效率自然就很低。
2. 内存对齐
1). 单结构体内部对齐规则(对齐模数):
(1)、第一个属性偏移为0
(2)、第二个元素开始,地址要放在 [该元素类型字节数] 和 [对齐模数] 两个中最小值的整数倍
(3)、整个结构所有元素计算完成后,整体放在 [属性中最大类型字节数] 和 [对齐模数]中最小值的整数倍
#include<vector>
#include<stdio.h>
// 查看对齐模数
#pragma pack(show)
// 设置对齐模数
#pragma pack(8)
// 对于自定义结构体,内存对齐规则如下:
// 1、第一个属性偏移为0
// 2、第二个元素开始,地址要放在 [该元素类型字节数] 和 [对齐模数] 两个中最小值的整数倍
// 3、整个结构所有元素计算完成后,整体放在 [属性中最大类型字节数] 和 [对齐模数]中最小值的整数倍
struct TestSt{
int a; // 0-3
char b; // 4-7
double c; // 8-15
float d; // 16-19
};
// 所以TestSt实际20个字节,但第3条,其最大类型double8字节,对齐模数8字节,所以取最小倍数最终为24字节
int main()
{
printf("%d\n",sizeof(TestSt)); // 结果为24
}
2). 更详细对齐规则:
(1)、数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
(2)、结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
(3)、结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐
3). 另外还有两个需要注意的点:
(1)、数组在内存中存储时是分开存储的,char类型的数组每个元素是 1Byte,内存对齐时按照单个元素进行对齐
union(联合体)类型中的数据共用内存,联合的所有成员共用一段内存空间,存储地址的起始位置都相同,一般来说最大成员的内存(2)、宽度作为union的内存大小,主要的原因是为了节省内存空间,默认的访问权限是公有的,但是它同样要遵守内存对齐的原则,特别是第3条规则
(3)、C++中空结构体占用 1Byte
(4)、C++中空类同样是占用 1Byte的内存空间(剑指offer 2.2.1节中中提到,当声明该类型的实例的时候,必须在内存中占有一定的空间,否则无法使用这些实例,占用多少内存由编译器决定)
// ---------------------test1---------------------
struct Test1 {
int a; // 0-3
double b; // 8-15
char c; // 16
};
// 这时一共用了17 Byte,但是sizeof所得的大小为24,这就用到了第3条规则,最后sizeof的大小还必须是内部最大成员长度的整数倍,不足的要补齐
// ---------------------test2--------------------
struct Test2 {
int a; // 0-3
double b; // 8-15
char c[6]; // 16-21
};
// 共22字节,但规则3,最终24字节
// ---------------------test3-------------------
struct Test {
int a; // 0-3
double b; // 8-15
char c; // 16
}; // 共24字节
struct Test3 {
int a; // 0-3
Test d; // 8-31 ,大小为24
double b; // 32-39
char c; // 40
};
// 共41字节,根据规则3,最终48字节
// ---------------------test4------------------
struct Test4 {
int a; // 0-3
Test d; // 8-31
char c; // 32
};
// 共33字节,规则3,最终40字节
// ----------------------test5-----------------
union Test5{
char a[20];
int b;
float c;
};
// 共20字节,20是b,c倍数,所以最终20
// ----------------------test6----------------
union Test6{
char a[4];
int b;
double c;
};
// 共8字节,8是b,c倍数,所以最终8
// ----------------------test7-----------------
union Test7{
char a[20];
int b;
float c;
double d;
};
// 共20字节,但20不是d倍数,最终24
int main()
{
printf("t1:%d,t2:%d,t3:%d,t4:%d,t5:%d,t6:%d,t7:%d\n",
sizeof(Test1),sizeof(Test2),sizeof(Test3),
sizeof(Test4),sizeof(Test5),sizeof(Test6),
sizeof(Test7));
}
更深入一些:
enum color {red , green, blue};
class A
{
public:
int i; // 0-3
union U
{
char buff[13]; // 0-12
int i;
}u; // 共13字节,但根据规则3,i的最小倍数,所以最终16字节。对齐模数int型4, 偏移:4-19
void foo(){}
typedef char* (*f)(void*);
color c; // 4字节,20-23
}; // 共24字节,满足4的倍数。
class B
{
public:
double i; // 0-7
char j; // 8
union U
{
char buff[13]; // 0-12
int i;
}u; // 共13字节,但根据规则3,i的最小倍数,所以最终16字节。对齐模数int型4, 偏移:12-27
void foo(){}
typedef char* (*f)(void*);
color c; // 4字节,28-31
char k; // 32
}; // 共33字节,按照规则3,需满足8的倍数,所以最终40字节
struct TestStl
{
double x; // 0-7
int y; // 8-b
char z; // c
std::vector<int> s; //24字节,对齐模数8, 16-39
char t; //40
}; //共41字节,按8的倍数,48字节
//43f91450,43f91458,43f9145c,43f91460,43f91478
struct TestStlMap
{
double x; // 0-7
int y; // 8-b
char z; // c
std::map<int,double> s; //48字节,对齐模数8, 16-63
char t; //64
}; //共65字节,按8的倍数,72字节
//d2a27d30,d2a27d38,d2a27d3c,d2a27d40,d2a27d70
int main()
{
TestStlMap t;
printf("%x,%x,%x,%x,%x\n", &t.x, &t.y,&t.z,&t.s,&t.t);
printf("%d,%d\n", sizeof(t.s), sizeof(t));
printf("%d,%d,%d\n",sizeof(color),sizeof(A),sizeof(B));
}