每每谈到和内存有关的内容时,我的积极性总能被充分调动起来,虽然不知为什么,对这些有种莫名的兴趣!
结构体
首先是我们的主角结构体本尊。在说之前,要先引入一个概念,叫做对齐数。那这个对齐数又是啥呢?
对齐数
顾名思义,就是对齐所占的内存数,可以这样简单理解,但实际上要复杂。其起源就是为了方便某些计算机系统互相不完全兼容的内存调用机制,使得它们之间也能够很好地共同读取同一份文件,并且提高效率。(以上为本人浅显理解,如有错误,欢迎指正)
举个很简单的例子,例如某台计算机只能以四个bit为单位读取某种数据,而此时要读取的数据大小也为4bit,并且恰好读取的起始位置位于要读取数据的前2个bit处,也就是说,本来可以一次读完的数据,竟因为数据“摆放”的不整齐而使访问时间翻了一番!由此可见规范的对齐数对于提升数据的访问效率有多么的重要!
另外,不同环境下的编译器一般会有一个默认对齐数,然而在实际使用时,这个对齐数可能也不会起作用,详见下面的例子吧。
结构体对齐数总则
当然,名字是我自己随便起的,看看就好。实际上,在结构体家族的内存储存中确实有着一套与对齐数紧密相关的规则,来服务于cpu,接下来我用一个小小的实例说明一下。
#include<stdio.h>
struct A
{
char a;
int b;
short c;
}a;
struct B
{
int b;
short c;
char a;
}b;
int main()
{
printf("%d\n",sizeof(struct A));
printf("%d\n",sizeof(struct B));
return 0;
}
如果去运行一下,可以发现大小分别是12和8.
那这究竟是怎么回事呢?内存的大小居然因为成员变量先后顺序不一样而不同?下面让我们一探究竟。
基于上一条我所说的原则,具体的规定是这样的:
第一个变量存储的位置为零偏移量,而后面每个变量所开始存储的位置的总偏移量都应是自身对齐数的整数倍;而这里的对齐数则是数据自身大小与编译器默认对齐数的较小值,例如struct A中int b的对齐数就是(4/8)中的4。
所以在存放完a之后,必须空出3个字节的位置,使b位置的起始位置偏移量为4,这才符合要求;同理,这样存完后,short c的起始偏移量恰好为8,是2的整数倍,因此c可以直接放在b后面,这样总大小来到了10个字节。那么还有两个字节去哪了呢?
原来,规则里还要求结构体的总大小要是最大对齐数(1/2/4)的整数倍,因为10不是4的整倍数,所以,又自动补充了两个字节的空间,因此,总大小来到了12个字节。
同理可以算出第二个struct B的大小为8,这正是由于对齐数规则的存在,所以,变量的定义顺序也是一门学问呢,如果把占内存小的变量放一起,可以一定程度上节省内存。
位段与联合体
这里稍微提一下。
位段实际上就是有特殊内存分配的一种结构体,这个东西很有意思啊,可以直接将内存细化到按比特位来进行分配,占用内存比较小的几个变量甚至可以存放到一个字节中,大大节省了空间。
而联合体则更加神奇,若有两个变量,一个为1字节,一个为4字节,则这两个变量的首字节便存放在一起,当改变其中一个变量时,另一个变量甚至也会改变,也正因此成为共同体。
说到这吧。