本文是对结构体进阶知识的讲解,如果没有掌握结构体的基本知识,可以先看下面的博客
从0到1助你掌握结构体
在从零到一助你掌握结构体这篇文章中,介绍了关于结构体的初阶知识,并且留了一些悬念,结构体的内存到底是如何分配的?以及为什么这么分配?下面,就让我们深入了解一下:
结构体内存对齐:
让我们观察以下代码,并思考结构体占的内存是多少:
typedef struct student
{
int age;
char name[3];
}student;
按照我们正常的思想,把结构体内部的变量所占的字节大小加在一起 4+3 = 7
应该是占7个字节,但实际上是这样吗?让我们打印一下:
我们可以看到,结构体的大小居然是八,他到底为什么是八呢?我们该如何计算呢?
在了解之前,我们先看一看,这结构体的八个字节,到底是怎么分配的:
这里引出的一条规则就是:结构体的大小必须是其实际对齐数的整数倍
如果不是,则要补齐,所以本应该大小为7的结构体,实际大小为8。
这里提到了一个对齐数的概念
对齐数:每个编译器都有一个默认的对齐数,在vs中是8,每个变量的对齐数都是自身大小与默认对齐数的较小者。
这个对齐数有什么作用呢?他决定了你在结构体中的位置,比如你是一个int变量,你的起始位置就不能是3,不能是5,不能是6,只能是4的整数倍。
举个例子:
typedef struct student
{
char a;
char b;
int c;
}student;
我们来计算一下这个结构体的大小,常理来说,a,b各占一个字节,c占四个字节,这是六个字节,再利用最后的补齐规则,补成4的整数倍,那就是八个字节
看起来确实是这样,但实际上在内存中的存储真的是这样吗?我们如何验证呢,这时候可以借助一个函数:offsetof函数,简单的看一下需要引用的头文件和参数类型:
这个函数是用于查看结构体变量在结构体中的偏移量的,偏移量是相当于结构体首地址的偏移程度,也可以理解成你在这片空间中起始地址是从第几块开始的。
方便理解,我们把内存画成图,如下:
这个图又两个要点:
结构体中的内存分布并不是我们想的那样,1,1,4,2,而是1,1,2,4,这是因为我们在给c空间的时候,他的对齐数是8和4中的较小值,就是4,所以他的起始地址必须是4的整数倍,所以他能从2的位置开始赋予内存,只能往下找,直到找到4,4的一倍,这样才能给他空间,所以是1,1,4,2。
对齐补的空间并不属于任何一个变量,这是如何体现的呢:
看这张内存图,我们给b一个256,整个内存的空间是:
01 00 cc cc 01 00 00 00 ,正常来说b的空间是 第一个空间,也就是01 后的空间,如果补齐的字节是属于b的,那我们给b一个256,他是能够存下的因为我现在有了三个字节,一个字节存储的是 8位,三个字节是24位,可实际上存储的数据是00,这就说明,后面的字节不属于b,而是一个独立的空间。
那么,我们再做一个小练习,若是结构体嵌套结构体呢?
typedef struct student
{
char a;
char b;
int c;
}student;
typedef struct std
{
student a1;
char b1;
int c1;
}std;
std的大小是多少呢?其各个元素的偏移量又是多少?我们来分析一下:
std中的第一个成员变量是student,他的空间还是按照,之前的规则算,对齐数也是一样,那就是8个,b1,对齐数是1,所以偏移量是8,c1必须是4的整数倍,所以是12,那么整个结构体所占的字节数就应该是8+1+3+4 = 16
小结
在这里我们小结一下结构体对齐的规则
1)
第一个成员变量在与结构体变量偏移量为0的地址处(起始地址)
2)
其他成员变量要对齐到该变量对齐数的整数倍地址处
3)
成员变量的对齐数是编译器默认的对齐数与该成员大小的较小值
4)
结构体的总大小为最大对齐数的整数倍
5)
在嵌套的情况下,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍处
为什么要内存对齐?
大体上能分为两种原因 :
1.平台原因
因为不是所有的硬件平台都能访问任意地址的任意数据的(比如只能访问4的整数倍的地址的数据),否则会抛出硬件异常。
2.性能原因
举个例子,寄存器一次能取出4个数据,你如果没进行内存对齐,你可能取到的是int 的一部分和一个char,那么你想取一个int,就可能需要两次,但是经过了内存对齐以后,你一次肯定能取到你想要的数
也可以说,内存对齐就是牺牲了内存的空间来换取运行效率的一种做法
后记
这只是结构体提升效率的一种方法,而且是以空间换时间,在下一篇博客中将会介绍节省空间的方法—>位段
其实在举例子的时候有一些纰漏,为了证明为了对齐申请的空间与成员变量无关的那个例子,没有确定他到底是截断的还是确实表示了256,只不过是循环成0了,这个大家可以多举几个例子证明或者推翻我的观点。