前言
对于字节对齐,是每个初学C语言者都比较容易晕的知识点,也是很多的公司在招聘的时候经常考到的知识点。但这个知识点对我们程序的优化以及理解计算机的运行原理又是至关重要的。所以今天,我们来这个知识点进行一下总结。
1. 为什么要字节对齐
简单来讲,就是为了提高数据读取的效率。那为什么字节对齐会提高数据存取的效率呢?主要原因是下面两个方面:
1) 外部总线,从内存中获取数据,并不是按照数据存储的小单位字节来的,而是4字节、8字节甚至更多,那这样,假如当前计算机是按照4字节读取数据的,那么,如果一个int类型的数据,在字节对齐的位置开始存放,则可以一次读取到,否则需要花两次才能取到这个数据。
2) 很多的cpu只从对齐的地址开始加载数据,而有的CPU这样做,就是为了更快一些。
2. 字节对齐的原则
1). 变量对齐
变量地址 % min(变量字节数, 机器位数/8) = 0
例:int a;
上边的例子等价于:变量地址%4=0
也就是,计算机会为我们开辟一块,长度为4字节,地址是4的整数倍的一块地址空间。
2). 结构体成员变量对齐
结构体成员变量地址 % min(变量字节数, 机器位数/8) = 0;
理解同1)。
3). 结构体变量对齐
结构体成员变量的大对齐方式相同
例:
struct test
{
char a;
int b;
short c;
}test;
上边的结构体中,大的对齐方式为4,则整个结构体的对齐方式为4,也就是结构体的内存地址是4的整数倍。
4). 结构体成员变量偏移对齐
结构体成员变量偏移 % min(变量字节数, 机器位数/8) = 0;
3. 详细实例
struct test
{
char a;
int b;
short c;
}test;
上边的结构体变量定义好之后,我们还是详细的来看一下,计算机为我们做了什么事情。
A:计算机首先,从一个是4的整数倍的地址开始准备开辟空间。(因为结构体的对齐方式与结构体中成员变量大的结构体方式相同。)比如这个地址值是0x7fff0100。
B: 成员变量a,char类型,根据对齐原则,这个成员变量的地址是1的整数倍即可。则是数组的地址即可。根据对齐,会一次开辟4字节的内存空间,a占用第一个字节。
C: 成员变量b,int类型,根据对齐原则,这个成员变量的地址是4的整数倍即可。则原来开辟的4字节,已经没有位置满足这个变量的对齐了,则会重新分配一个四字节的内存空间,将成员变量b放到这个四字节中,正好放下。
D: 后还有一个short类型的变量c,则系统又一次性的分配4字节,short c占用其中的两个字节。
将其装在内存中如下:
+---+---+---+---+---+---+---+---+---+---+---+---+
| a | | b | c | |
+---+---+---+---+---+---+---+---+---+---+---+---+
那么这个结构体的长度,也就是sizeof(test)=12;
那么接下来,我们换一种定义方式看一下:
struct test
{
char a;
short c;
int b;
}test;
+---+---+---+---+---+---+---+---+
| a | | c | b |
+---+---+---+---+---+---+---+---+
根据对齐原则,就是上边的这种存储方式,可以看到,这种方式的定义,只需要分配8字节的内存空间就够了。
那我们利用字节对齐,可以修改定义结构体的顺序,是可以达到节省空间的效果的。
另外,还会有一些场景,我们会通过指针的偏移的方式访问结构体内存的,这个时候,如果我们存在空洞,而写指针偏移的程序的人没有注意,是会非常容易出问题的,则这种场景,我们一般会人为的将结构体中的内存中的空洞,全部补齐。
比如,上边两种定义,我们分别来将其补齐。
struct test
{
char a;
char szResv[3];
int b;
short c;
short sResv;
}test;
struct test
{
char a;
char cResv;
short c;
int b;
}test;