内存对齐、内存补齐

xiaoxiao2021-02-28  65

1 引言

记得第一次接触这块知识是在3月底海康威视春招嵌入式岗位面试的时候。那时候面试官问了我一个问题:问下面代码所占字节的大小。 struct str{ int a; char b; }

当时对内存对齐与补齐也不太懂,直接说了一句int类型占4个字节,char类型占1个字节,总共占5个字节。当时面试官也没说对错。后来回来和室友讨论才知道还要考虑内存补齐。所以在32位系统中一共要占8个字节。

最近恰好在牛客网刷题时候又遇到了这个问题,所以彻底学习了一下。

2 内存对齐、补齐

许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址是某个数k(通常要求为4或8的倍数),这就是所谓的内存对齐。

2.1 内存对齐规则

struct或者union成员对齐规则:1、第一个数据成员放在offset为0的地方,对齐按照对齐系数和自身所占用的字节数中,两者比较小的那个进行对齐。

2.2 内存补齐规则

在struct或者union数据成员完成各自对齐之后,struct或者union本身也要对齐,对齐按照对齐系数和struct或者union中最大数据成员长度中比较小的那个进行。先局部成员对齐,然后再全局对齐。

2.3 对齐的优点

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

3 例题讲解

这是一个摘自牛客网的例题:

struct One{ double d; char c; int i; } struct Two{ char c; double d; int i; } 问在#pragma pack(4)和#pragma pack(8)的情况下,结构体的大小分别是多少?答:首先在#pragma pack(4)情况下,按照内存对齐的原则,struct One中的d占据0-7字节(offset=0开始,double类型占8个字节)。第二个成员c占据8字节(char类型占1个字节,对齐系数是4,两者中1较小,所以偏移地址必须是1的倍数,最近的1的倍数是8,所以i从8开始占1个字节)。第三个成员i占据12-15字节(int类型占4个字节,对齐系数是4,两者中4较小,所以偏移地址必须是4的倍数,最近的4的倍数是12,所以i从12开始占4个字节,前面的9-11三个字节对齐。)。而struct One中最大的数据成员是double型,占8个字节,对齐系数是4,两者中4较小,内存补齐的的地址必须是4 的倍数,而下一个字节刚好是16,是4的倍数,所以不需要内存补齐。所以在#pragma pack(4)情况下,struct One所需内存为16字节。struct Two中的c占据0字节(offset=0开始,char类型占1个字节)。第二个成员d占据4-11字节(double类型占8个字节,对齐系数是4,两者中4较小,所以偏移地址必须是4的倍数,最近的4的倍数是4,所以i从4开始占8个字节,前面的1-3三个字节对齐。)。第三个成员i占据12-15字节(int类型占4个字节,对齐系数是4,两者中4较小,所以偏移地址必须是4的倍数,最近的4的倍数是12,所以i从12开始占4个字节)。而struct Two中最大的数据成员是double型,占8个字节,对齐系数是4,两者中4较小,内存补齐的的地址必须是4 的倍数,而下一个字节刚好是16,是4的倍数,所以不需要内存补齐。所以在#pragma pack(4)情况下,struct Two所需内存为16字节。在#pragma pack(8)情况下,按照内存对齐的原则,struct One中的d占据0-7字节(offset=0开始,double类型占8个字节)。第二个成员c占据8字节(char类型占1个字节,对齐系数是8,两者中1较小,所以偏移地址必须是1的倍数,最近的1的倍数是8,所以i从8开始占1个字节)。第三个成员i占据12-15字节(int类型占4个字节,对齐系数是8,两者中4较小,所以偏移地址必须是4的倍数,最近的4的倍数是12,所以i从12开始占4个字节,前面的9-11三个字节对齐。)。而struct One中最大的数据成员是double型,占8个字节,对齐系数是8,两者中8较小,内存补齐的的地址必须是8 的倍数,而下一个字节刚好是16,是8的倍数,所以不需要内存补齐。所以在#pragma pack(8)情况下,struct One所需内存为16字节。struct Two中的c占据0字节(offset=0开始,char类型占1个字节)。第二个成员d占据8-15字节(double类型占8个字节,对齐系数是8,两者中8较小,所以偏移地址必须是8的倍数,最近的8的倍数是8,所以i从8开始占8个字节,前面的1-7七个字节对齐。)。第三个成员i占据16-19字节(int类型占4个字节,对齐系数是8,两者中4较小,所以偏移地址必须是4的倍数,最近的4的倍数是16,所以i从16开始占4个字节)。而struct Two中最大的数据成员是double型,占8个字节,对齐系数是8,两者中8较小,内存补齐的的地址必须是8 的倍数,而下一个字节是20,不是8的倍数,所以需要内存补齐。最近的8的倍数是24,所以需要再补4位,即占用20-23字节用来结构体内存补齐。所以在#pragma pack(8)情况下,struct Two所需内存为24字节。

4 自测题

在32位机器上,下列代码中sizeof(a)的值是()

class A { int i; union U { char buff[13]; int j; }u; void foo(){} typedef char* (*f)(void*); enum{red,green,blue}color; }a; 答案是24=4+13+3+0+0+4。其中成员函数不管是否为空,都不会占用空间,有虚函数除外,有虚函数时会多一个指针(内存+4);typedef是声明,不占用空间;枚举则是int,占4个字节。
转载请注明原文地址: https://www.6miu.com/read-40990.html

最新回复(0)