在C语言开发当中会遇到这样的情况:
1 #include <stdio.h> 2 3 struct test { 4 int a; 5 char b; 6 }; 7 8 int main(int argc, const char * argv[]) 9 { 10 printf("%lu\n", sizeof(struct test)); 11 return 0; 12 }
sizeof操作输出的结果是8,可是int和char类型的长度加起来只有5,那么为什么输出了8呢?
这就牵扯到结构体的内存对齐问题,事实上,结构体中的变量在内存当中并不是以一种连续紧凑的方式存储的。
上面这个例子是一种最简单的情况,来看一下它的内存排布:
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | |
0x10 | a | b | 空位 | |||||||||||||
0x20 |
可见,从0x15到0x18都是空位,为什么要这样存储呢?因为计算机字长为4,处理结构体变量时,需要一次读入32位,也就是4个字节,那么假定两个结构体变量a和b,a从0x10开始,b从0x15开始的话,则需要两次寻址,第一次是读出0x14到0x17,取出后三位,第二次是从0x18到0x1B,取出第一位,然后做位运算,由于变量处理很频繁,这样就增加了性能的开销,那么只能采取用空间换时间的策略了。
假设有两个结构体变量,那么内存排布是这样的:
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | |
0x10 | a | b | 空位 | a | b | 空位 | ||||||||||
0x20 |
这便是内存对齐的第一个方面——结构体的末尾如果没有对齐,则会留出空位来,为排在它前后的变量提供寻址上的便利。
事实上,结构体的大小,必然是其中占用空间最大的类型的长度的整数倍。
接下来改变一下我们的结构体:
1 struct test { 2 char b; 3 int a; 4 };
它的长度同样是8,内存排布如下:
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | |
0x10 | b | 空位 | a | |||||||||||||
0x20 |
同样基于上面提到的原因,如果将a放在0x11的位置,需要两次寻址。那么,结构体内存排列的一个重要原则就是要得到最少的寻址次数,显然,为了方便找到int型的a,需要在b后面空出3个字节的空间来。
下面举一个稍微复杂一点的例子:
1 struct test { 2 char a; 3 int b; 4 char c; 5 char d; 6 double e; 7 char f; 8 };
它的长度是32,看一下内存排列:
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | |
0x10 | a | 空位 | b | c | d | 空位 | ||||||||||
0x20 | e | f | 空位 |
为了让变量b寻址方便,在a的后面空出了3个字节,而c和d无论如何都要寻址一次,所以顺序排下去就可以了,而e长度为8,按说可以从0x1C开始排,但是这里需要和类型本身的长度8进行对齐,则排在了0x20的位置,于是乎d的后面又空出了6个字节的空间,最后f排在e的后面,之后为了结构体本身和最长类型的长度8对齐,后面又留出了7个字节的空位。
显而易见结构体当中变量的顺序是会影响存储空间的,在设计结构体的时候需要进行优化,比如将上面的结构体改写成下面的形式可以节省空间:
1 struct test { 2 char a; 3 char c; 4 char d; 5 char f; 6 int b; 7 double e; 8 };
这种排列只占用16字节,内存如下:
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | |
0x10 | a | c | d | f | b | e | ||||||||||
0x20 |
可见,内存对齐是一种空间换时间的手段,当然,为了节约内存,可以通过如下代码令结构体紧凑排列:
1 #pragma pack(1)