编译器按照成员的顺序一个接一个地给每个成员分配内存,但是由于要考虑到正确的边界对齐要求,成员之间会出现用于填充的额外内存空间(内存对齐)。 为什么有边界对齐? 当内存中的值合理对齐时,很多机器能非常高效地访问,而且,有些机器根本就不能访问没有对齐的地址。 结构的内存对齐主要有一下几个原则: 1)结构体变量的首地址是其最长基本类型成员的整数倍; 备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为对齐模数。 2)结构体每个成员相对于结构体首地址的偏移量(offset)都是此成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,否则,在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。 4)结构体内类型相同的连续元素将在连续的空间内,和数组一样。 5)如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。
例1:struct { short a1; short a2; short a3; }A;
struct{ long a1; short a2; }B; sizeof(A) = 6;这个很好理解,三个short都为2。 sizeof(B) = 8;这个是不是比预想的大2个字节?long为4,short为2,整个为8。 例2:struct A{ int a; char b; short c; };
struct B{ char b; int a; short c; }; sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。 sizeof(B) = 12;是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。 深究一下,为什么是这样,我们可以看看内存里的布局情况。 a b c A的内存布局:1111, 1*, 11 b a c B的内存布局:1***, 1111, 11** 其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。 B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。 再看一个结构中含有结构成员的例子: 例3:struct A{ int a; double b; float c; }; struct B{ char e[2]; int f; double g; short h; struct A i; }; sizeof(A) = 24;这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。 sizeof(B) = 48;看看B的内存布局。 11* *,1111,11111111, 11 * * * * * *, 1111* * * *, 11111111, 1111 * * * * i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。
由于内存对齐会造成空间浪费,所以,可以在声明中对结构的成员进行重排,让那些对边界要求严格(宽度大)的成员先出现(数组成员应该根据它成员的大小来判断其大小,而不是整个数组的大小),对边界要求最弱的最后出现。 但是有时候这样会导致结构的可读性和可维护性降低,当程序创建几百个甚至几千个结构时,减少内存浪费的要求比程序的可读性更为急迫。这时,可以在声明中添加注释来减少可读性方面的损失。
使用宏确定结构体中的成员的偏移量: 使用ANSI C 如: 1/* offsetof example */ 2#include 3#include 4 5struct foo{ 6 char a; 7 char b[10]; 8 char c; 9}; 10 11intmain(void) 12{ 13 printf("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a)); 14 printf("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b)); 15 printf("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c)); 16 17 return0; 18} 19 20/* 21 * output: 22 * offsetof(struct foo,a) is 0 23 * offsetof(struct foo,b) is 1 24 * offsetof(struct foo,c) is 11 25 * 26 */ |