程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言的struct的數據成員對齊

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

編輯:關於C
  一、引言:     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; } 復制代碼     五、排列成員順序,使得結構體占有存儲空間最小    這個題目更有意思,我這裡暫時不貼,有想法的歡迎回復。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved