面试必考 - 结构体内存对齐,还有人不会?

时间:2022-11-06 19:57:06


很多人在编写代码实现功能的时候,或多或少都会接触到结构体的使用,在很多的编语言中,结构体都是很多数据结构的重要的组成部分。

在嵌入式的项目开发中,很多时候芯片的内存资源都是有限的,为了代码更加的优化和高效,尽可能的合理使用内存资源,在使用结构体这类结构时,往往是要考虑结构所占的内存大小的。以此方便我们调整顺序,尽可能的节省内存资源。(土豪略过,不做讨论!)

使用结构体,需要考虑结构的内存对齐问题,现在逐一展开讲讲!

1、 什么是内存对齐?

我们都知道,定义的变量(元素)是要按照顺序一个一个放到内存中去的,它们也不一定就是紧密排列的,是要按照一定的规则就行排放的,这就是内存对齐。

对结构体来说,元素的存储从首地址开始,第一个元素的地址和整个结构体的首地址相同,其他的每个元素放置到内存中时,它都会认为内存是按照元素自己的大小来划分空间的,所以元素放置在内存中的位置一定会在元素自己宽度(字节数)的整数倍上开始,这就是所谓的结构体内存对齐问题。

特别有意思的是,C语言同意使用者自行确定内存对齐的设置,通过伪指令 #pragma pack (n) 可以重新设定内存对齐的字节数。这个后面会讲到!

2、 为什么要有内存对齐?

这真是一个好问题!从网上了解到的几个原因:

(1)考虑平台的原因。实际的硬件平台跑代码是有所区别的,一些硬件平台可以对任意地址上的任意数据进行访问,而有一些硬件平台就不行,就是有限制,所以内存对齐是一种解决办法。

(2)考虑性能的原因。CPU访问内存时,如果内存不对齐的话,为了访问到数据的话就需要几次访问,而对齐的内存只需要访问一次即可,提高了CPU访问内存的速度。

3、结构体的内存对齐规则是什么?

每当有用到结构体的时候,总会考虑这个结构体实际应该要占用多少的内存,是否还有优化的空间。特别是在面试时,结构体的内存对齐问题是很多面试会考到,也会经常被提及问起,属于高频考点了!

话不多说,直接奉上结构体的内存对齐的判别方法,方便大家快速算出结构体所占的内存大小。

这里先规定一下:内存对齐值称为内存对齐有效值,这个值可以是1、2、4、8、16,所以先规定一下。

规则:

规则1,结构体第一个成员一定是放在结构体内存地址里面的第1位。

规则2,成员对齐规则:除了第一个成员,之后的每个数据成员的对齐要按照成员自身的长度和内存对齐有效值进行比较,按两者中最小的那个进行对齐,即偏移的倍数。

规则3,结构体整体对齐规则:数据成员完成对齐之后,对整个结构体的大小进行对齐。按照结构体的大小必须要是内存对齐有效值和结构体中最大数据成员长度两者中的最小值的整数倍,不足的在后面补空。

4、 规则的验证

规则已经在上面摆出来了,那怎么知道对不对呢?那就只能现场分析一波。

举个例子:

编译器内存对齐有效值=4,结构体如下:

typedef struct 
{
int a;
char b;
}StructDef_t;

大家猜猜这个结构体占了多少个内存?

5个?

NO、NO、NO!!!

正确答案是:8个!

8个是怎么来的呢?假设地址从0开始,分析如下:

首先,a为int型占4个字节,放在最开始的位置,即offset=0的位置,放在0、1、2、3的地址。

然后,用规则2:b占一个字节,内存对齐有效值为4,所以b要相对于结构体首地址的偏移要为1的倍数,放在4的地址。

最后,从上面的一步我们知道了这个结构体内的成员对齐之后占了五个字节。用规则3:结构体内最大的成员占4个字节,内存对齐有效值为4,所以整个结构体的大小要为4的倍数,5不是4的倍数,所以要在后面补齐,为8个字节。

所以,最终这个结构体占用8个字节!

这个过程可以用下面的图示进行演示,方便加深了解,如下图:

面试必考 - 结构体内存对齐,还有人不会?

5、强化训练

如下:

#include<stdio.h>
typedef struct
{
int i;
char c1;
char c2;
}Test1;

typedef struct{
char c1;
int i;
char c2;
}Test2;

typedef struct{
char c1;
char c2;
int i;
}Test3;

int main()
{
printf("%d\n",sizeof(Test1)); // 输出8
printf("%d\n",sizeof(Test2)); // 输出12
printf("%d\n",sizeof(Test3)); // 输出8
return 0;
}

     这三个结构体,可以运用上面的三个规则去验证一遍。

注意:从上面的三个结构体中可以发现,通过调换结构体里面的数据成员的位置,可以改变结构体占空间的大小,这是因为数据元素位置不同,对齐之后的结果也不同。这种方式可以用于结构体的空间优化,通过调整元素的位置,减少内存的占用!

6、自定义内存的对齐值

C语言中是允许用户自己定义内存对齐值的,使用一个伪指令即可,如下:

#pragma pack (n)    // 自定义对齐值,n=1,2,4,8,16
#pragma pack ( ) // 取消自定义字节对齐

6.1、1 字节对齐

如下代码:

#include<stdio.h>

#pragma pack (1)

typedef struct
{
int a;
char b;
}StructDef_t;


int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}

#pragma pack ()

在1字节内存对齐情况下,这里的结构体占5个字节!

6.2、2 字节对齐

如下代码:

#include<stdio.h>

#pragma pack (2)

typedef struct
{
int a;
char b;
}StructDef_t;


int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}

#pragma pack ()

在2字节内存对齐情况下,这里的结构体占6个字节!

6.3、4 字节对齐

如下代码:

#include<stdio.h>

#pragma pack (1)

typedef struct
{
int a;
char b;
}StructDef_t;


int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}

#pragma pack ()

在4字节内存对齐情况下,这里的结构体占8个字节!

以此类推,可以自行验证!

面试必考 - 结构体内存对齐,还有人不会?