您的位置:首页 > 编程语言 > C语言/C++

内存对齐、内存补齐

2017-08-06 00:25 113 查看

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个字节。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息