很久之前就想将内存对齐这块儿知识点总结记录下来,无奈本人患有very very严重的拖拉症,直到今天才下决心将这件事儿解决掉,废话不多说了,开工!(ps:本人所用编译器version为 gcc Ubuntu4.9.2-10 ubuntu13 4.9.2)
结构体到底占多大的空间呢?先看一下下面这道题的输出结果:
#include<stdio.h>
typedef struct test
{
char a;
int b;
double c;
}TEST;
int main(void)
{
TEST test1;
printf("%ld\n",sizeof(test1));
return 0;
}
不妨大胆猜想,结构体所占空间是不是其成员所占空间的代数和呢??按照这个猜想,该题的答案应该为13,然而事实却让人大跌眼镜,本题输出的结果为16。google一下知这是因为计算机中存在一种叫做内存对齐的机制导致了该结果的发生。
在计算机中通常会让CPU从内存中一次读取若干个字节的数据,而不是一次只读取一个字节的数据,这样的好处是提高了计算机的效率,然而坏处也显而易见。
假设CPU一次从内存中读取四个字节的数据,而现在内存中存在一个char型的数据和一个int型的数据,如果内存不对齐,当CPU第一次跨越四个字节寻址找到了一个char型的数据,而此时CPU的指向到了int型的中间区域,导致这个int型变量未找到,然后CPU会返回去再次寻找,直到找到该int型变量。这样不但没能提高效率,反而增加了CPU的负担。因此我们通常会在第一个char型变量后边填充一部分数据来保证每次寻址时地址都是该数据的整数倍,这样就避免了上述“错误”的发生,也就是所谓的内存对齐。
内存对齐的规则很简单:
一、起始位置为成员数据类型所占内存的整数倍,若不足则不足部分用数据将内存填充为该数据类型的整数倍。
二、结构体所占总内存为其成员变量中所占空间最大数据类型的整数倍。
假设上题中结构体变量是从零号内存开始存储,则char型变量占一个字节,而后int型变量发现起始位置在一号内存处,并不满足起始位置为int型所占4字节整数倍的要求,故将一二三号内存填充满,从四号内存处开始存储该int型成员,当该int型成员存储完成后已经用了八个字节的空间,因此此时double型成员的起始位置为第八号内存,满足第一条条件,所以double型开始存储,存储完成后该结构体变量刚好占16个字节,刚好是最大数据类型double八个字节的整数倍,所以存储完成,因此该结构体变量占了16个字节。
再看这一题
#include<stdio.h>
typedef struct test
{
char a;
double b;
int c;
}TEST;
int main(void)
{
TEST test1;
printf("%ld\n",sizeof(test1));
return 0;
}
这题乍一看和上一题的区别仅仅是将int型和double型调换了位置,那结果呢?结果肯定是不一样的,按照上述分析步骤走一遍看看,先是char型从零号开始存储,因为要遵循第一条原则故double型从八号开始存储,一到七号被填充,double型存储完后int型从16号位置开始存储,存储完成后发现此时一共占了20个字节的空间,不满足第二条规则,因此又将20到23号位置填充,所以该结构体变量占24个字节,从这里就可以看出在定义结构体时成员变量的顺序会影响其在内存中所占的资源大小,因此我们在定义结构体时要调整顺序使其尽可能在内存中所占空间最小。
其实内存对齐的规则和编译环境也是有一定的关系,其默认对齐系数也有差异,并且也是可以通过预处理命令#pragma pack(n)来改变的,这里所说的规则只是一个常见规则,不过话说回来万变不离其宗,其实本质都是一样的