C語言的struct的數據成員對齊


  一、引言:

  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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM