C语言的struct的数据成员对齐

浏览:
字体:
发布时间:2013-12-09 23:23:14
来源:
  一、引言:
 
  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;
}
复制代码
 
 
五、排列成员顺序,使得结构体占有存储空间最小
 
 这个题目更有意思,我这里暂时不贴,有想法的欢迎回复。
>更多相关文章
24小时热门资讯
24小时回复排行
资讯 | QQ | 安全 | 编程 | 数据库 | 系统 | 网络 | 考试 | 站长 | 关于东联 | 安全雇佣 | 搞笑视频大全 | 微信学院 | 视频课程 |
关于我们 | 联系我们 | 广告服务 | 免责申明 | 作品发布 | 网站地图 | 官方微博 | 技术培训
Copyright © 2007 - 2023 Vm888.Com. All Rights Reserved
粤公网安备 44060402001498号 粤ICP备19097316号 请遵循相关法律法规
');})();