> 文章列表 > 内存对齐总结

内存对齐总结

内存对齐总结

内存对齐总结


参考文章:

  • https://www.nowcoder.com/issue/tutorial?tutorialId=93&uuid=8f38bec08f974de192275e5366d8ae24
  • https://zhuanlan.zhihu.com/p/30007037

1.什么是内存对齐?

计算机系统对基本类型数据在内存中存放的位置有限制,

它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间,各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。

看下面程序,理论上char name[10]占10byte,age占4byte,weightheight各占8字节,那么将它们放到一个结构体中应该占30byte;但是实际上得到的结果是32byte,这就是内存对齐所导致的。

#include<iostream>
using namespace std;class People {
public:char name[10];int age;//4double weight;//8double height;//8void say(string word){};void run(){};
};int main() {People p;cout << "size of string = " << sizeof(string) << endl;cout << "size of People = " << sizeof(People) << " size of p = " << sizeof(p) << endl;//内存对齐cout << "address of name = " << &p.name << endl;cout << "address of age = " << &p.age << endl;cout << "address of weight = " << &p.weight << endl;cout << "address of height = " << &p.height << endl;return 0;
}

在这里插入图片描述

2.为什么要进行内存对齐?

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

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性即所谓的对齐,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。

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

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

3.内存对齐的规则

  1. 结构体的对齐值是其成员的最大对齐值
  2. 编译器可以设置一个最大对齐值,类型的实际对齐值是该类型的对齐值与默认对齐值取最小值得来。
  3. 内存占用大小与类成员属性的先后顺序有关,与编译器内存对齐的设置有关
  4. 成员方法对类占用内存没有任何影响(只和成员属性有关),成员方法中隐藏一个this指针,节省了内存开销

case1:利用#pragma pack(1)修改内存对齐方式为1byte

#include<iostream>
#pragma pack(1)//修改内存对齐的规则为1byte
using namespace std;class People {
public:char name[10];int age;//4 -> 8double weight;//8double height;//8void say(string word){};void run(){};
};int main() {People p;cout << "size of People = " << sizeof(People) << " size of p = " << sizeof(p) << endl;//内存对齐cout << "address of name = " << &p.name << endl;cout << "address of age = " << &p.age << endl;cout << "address of weight = " << &p.weight << endl;cout << "address of height = " << &p.height << endl;return 0;
}

在这里插入图片描述

case2:内存对齐导致类成员占用内存大小与书写类成员属性先后有关

#include<iostream>
using namespace std;class Test {int a;double c;int b;
};int main() {cout << "size of Test = " << sizeof(Test) << endl;return 0;
}

在这里插入图片描述

#include<iostream>
using namespace std;class Test {int a;int b;double c;
};int main() {cout << "size of Test = " << sizeof(Test) << endl;return 0;
}

在这里插入图片描述

case3:结构体的对齐值是其成员的最大对齐值

#include<iostream>
using namespace std;class Test {int a;int b;int c;
};int main() {cout << "size of Test = " << sizeof(Test) << endl;return 0;
}

在这里插入图片描述

case4:类内存使用的特殊情况:如果一个类中没有定义任何内容,则编译器会自动在类中加入一个char类型使其占用1byte大小,

#include<iostream>
using namespace std;class Test {};int main() {cout << "size of Test = " << sizeof(Test) << endl;return 0;
}

在这里插入图片描述

4.内存对齐的使用场景?

在客户端与服务端交互、进行网络通讯时,当这样一个类的结构被序列化出来之后,以上例为例,非内存对齐的情况下为32byte,

  1. 如果客户端做了内存对齐,而服务端没有做内存对齐,则将导致通信协议解析失败,
  2. 另外在客户端与服务端之间的通讯内容多了2byte,网络包大小增加并且资源浪费(protobuf删除掉不相关的字节)

5.几个内存对齐的案例

内存对齐应用与3种数据类型:struct、class、union

  1. 数据成员对齐规则:结构(struct)或联合(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始。
  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储。(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。
  3. 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的"最宽基本类型成员"的整数倍。不足的要补齐。(基本类型不包括struct/class/uinon)。
  4. sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。
struct example {int a[5];char b;double c;
} test_struct;int result = sizeof(test_struct);
/* 如果我们不考虑字节对齐,那么内存地址0x0021不是double(8Byte)的整数倍,所以需要字节对齐,
那么此时满足是double(8Byte)的整数倍的最小整数是0x0024,说明此时char b对齐int扩充了三个字节。
所以最后的结果是result=32 */union example {int a[5];char b;double c;
};
int result = sizeof(example);
/* 如果以最长20字节为准,内部double占8字节,这段内存的地址0x00000020并不是double的整数倍,
只有当最小为0x00000024时可以满足整除double(8Byte)同时又可以容纳int a[5]的大小,
所以正确的结果应该是result=24 */struct example {char b;double c;int a;
} test_struct;int result = sizeof(test_struct);
/* 字节对齐除了内存起始地址要是数据类型的整数倍以外,还要满足一个条件,
那就是占用的内存空间大小需要是结构体中占用最大内存空间的类型的整数倍,
所以20不是double(8Byte)的整数倍,我们还要扩充四个字节,
最后的结果是result=24 */