一、引言:
sizeof是c语言中的一个运算符,用来求某个变量或者类型的长度,CSDN有篇文章介绍sizeof的特点介绍的比较详细,我写这篇文章主要是介绍struct的数据成员对齐。C语言的struct成员对齐与操作系统有关,在window与linux上的表现不同,先来看一个例子:
1 #include <stdio.h> 2 typedef struct{ 3 int num1; 4 int num2; 5 double num3; 6 7 }A; 8 typedef struct{ 9 int num1; 10 double num3; 11 int num2; 12 }B; 13 14 int main(void) { 15 printf("A:%d\n",sizeof(A)); 16 printf("B:%d\n",sizeof(B)); 17 return 0; 18 }
二、windows的对齐情况
上面这段程序在windows下执行打印的是:
A:16
B:24
为什么数据成员一样,只是成员的顺序不同,导致结构体所占的空间会不同,这就是数据对齐的原因,为了提高存储器的访问效率,避免读一个成员数据访问多次存储器,操作系统对基本数据类型的合法地址做了限制,要求某种类型对象的地址必须是某个值K的整数倍(K=2或4或8)。Windows给出的对齐要求是:任何K(K=2或4或8)字节的基本对象的地址都必须是K的整数倍。在上面的示例中,num1和num2为int占4个字节,num3为double占8了字节,结构体A、B的数据对齐情况分别如下:
上面的是结构体A的对齐情况,下面的是结构体B的对齐情况,图中的灰色部分为对齐填充部分,不代表有效数据。可以看到A的分布很紧凑,没有留下空隙,而B中,有两段空隙,因为数据A不需要填充就能满足K(这里K=4、8)字节的对象的起始地址是K的整数倍了。而B中,第一个数据成员是num1,大小为四个字节,接下来的是num3,大小为8个字节,num3不能紧接在num1的后面,因为4不是8的整数倍,因此需要填充4个字节,这样num3的起始地址就在8上,满足要求,之后的num2接在num3后,起始地址为16。有人会问,为什么B占用的是24个字节,而不是20个字节,从上面的图中,也看出,用20个字节刚好装下了num1、num2、num3这三个元素,并且这三个元素都满足了对齐要求,为什么num2后面还要填充4个字节?事实上,如果只有一个B对象,20字节确实是满足对齐要求的,但如果我们声明一个类型为B的数据:B b[3],每个B对象只用20字节,则其数据偏移情况如下:
可以看到,b[1]的num3的起始地址是28,不满足8的整数倍的要求,这就是B为什么要24字节的原因,为了所有的数据满足”任何K(K=2或4或8)字节的基本对象的地址都必须是K的整数倍“的要求,必须是结构体的整体大小必须是最大的K的整数倍。
三、linux的对齐情况
以上是windows的对齐要求,如果在linux上执行前面的示例,输出A、B所占的字节数都是16。Linux的对齐要求是:2字节类型的数据(如short)的起始地址必须是2的整数倍,而较大(int *,int double ,long)的数据类型的地址必须是4的整数倍。linux的对齐要求比windows宽松,这样会更加充分的利用存储空间,但是访问效率没有window好。linux下结构体B的对齐情况如下:
这里是针对32位的系统,对于X86-64,linux与windows一样,要求K字节的数据类型是K的倍数。
以上是对struct的数据对齐的简单介绍,我想,这个数据对齐可以出两个面试题,一个是已知道结构体定义,求成员的起始地址和结构体大小;另一个是,已知结构体定义,如何排列成员变量的顺序,使得整个结构体占有的存储空间最小。
四、计算结构体的大小和各个成员的起始地址
这个题目是比较简单的,只要把对齐要求理解了,我们只前往后处理每一个变量,只要当前放入的变量满足对齐要求,然后递归求后门的变量,直接上代码了:
#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 100 char *vars[MAX];//保存变量名 int lens[MAX];//保存变量的字节长度 int start[MAX];//保存变量的起始地址 int ALIGN; /*********扫描成员变量和长度,每一组输入由变量名和长度组成**********/ int scanfStruct(){ char var[20],*p; int len; int index = 0; printf(">>"); while(scanf("%s %d",var,&len) && var[0] != '$'){ p = (char *)malloc(strlen(var)); strcpy(p,var); vars[index] = p; lens[index] = len; printf(">>"); // printf("%s:%d\n>>",vars[index],lens[index]); index ++; } return index; } /*********计算linux的对齐长度*********/ int getLinuxAlign(int *lens, int n){ int i=0; int align = 1; for(i = 0;i< n;i++){ if(align < 2 && *(lens +i) == 2) align = 2; if(align <4 && *(lens +i) >= 4) align = 4; } return align; } /*********计算windows的对齐长度*********/ int getWindowsAlign(int *lens, int n){ int i=0; int align = 1; for(i = 0;i< n;i++){ if(align < 2 && *(lens +i) == 2) align = 2; if(align <4 && *(lens +i) == 4) align = 4; if(align <8 && *(lens +i) == 8) align = 8; } return align; } /****** 递归计算各个变量的数据偏移以及结构体大小 i表示正在处理第i个变量,curAddr表示当前地址 size表示结构体的大小,n表示变量个个数 ******/ void getStart(int i,int *curAddr, int *size, int n){ if(i >= n) return; start[i] = *curAddr;//第i个变量的首地址为当前地址 *curAddr += lens[i]; *size += lens[i]; if( *curAddr % ALIGN ==0)//当前地址能被对齐长度整除,直接递归处理下一个变量 getStart(i+1,curAddr,size,n); else{//当前地址不能被对齐长度整除 int blank = ALIGN - (*curAddr % ALIGN);//需要填充的大小 if(i == n-1){//已经是最后一个变量,只需将结构体大小扩充一下 *size += blank; return; }else if(lens[i+1] > blank){//下一个变量的大小大于填充大小,填充后,递归处理下一个变量 *curAddr += blank; *size += blank; getStart(i+1,curAddr,size,n); }else{ i++; while(lens[i] <= blank){//只要下一个变量的大小小于填空空白大小 start[i] = *curAddr; *curAddr += lens[i]; *size += lens[i]; blank = ALIGN -(*curAddr % ALIGN); if(i == n-1){ *size += blank; return ; } i++; } getStart(i,curAddr,size,n); } } } void printStart(int n){ int i=0; for(i=0; i<n; i++) printf("%s(%d):%d\n",vars[i],lens[i],start[i]); } int main(){ int n; int curAddr = 0; int size = 0; n = scanfStruct(); //ALIGN = getLinuxAlign(lens,n); ALIGN = getWindowsAlign(lens,n); getStart(0,&curAddr,&size,n); printf("align:%d\n",ALIGN); printf("\nsize:%d\n",size); printStart(n); return 0; }
五、排列成员顺序,使得结构体占有存储空间最小
这个题目更有意思,我这里暂时不贴,有想法的欢迎回复。
感谢阅读,欢迎回复!
转载请注明出处:www.cnblogs.com/fengfenggirl