1. 程式人生 > >關於sizeof與#pragma pack導致結構體大小變化的問題

關於sizeof與#pragma pack導致結構體大小變化的問題

對於結構體,在使用sizeof的時候會進行位元組的對齊,對齊的規則如下: 

1) 結構體變數的首地址能夠被其最寬基本型別成員的大小所整除; 
   備註:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基本資料型別,然後尋找記憶體地址能被該基本資料型別所整除的位置,作為結構體的首地址。 
2) 結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding); 
  備註:為結構體的一個成員開闢空間之前,編譯器首先檢查預開闢空間的首地址相對於結構體首地址的偏移是否是本成員的整數倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的位元組,以達到整數倍的要求,也就是將預開闢空間的首地址後移幾個位元組。 

3)結構體的總大小為結構體最寬基本型別成員大小的整數倍,如有需要,編譯器會在最末一個成員之後加上填充位元組(trailing padding)。 
  備註:結構體總大小是包括填充位元組,最後一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個位元組以達到本條要求。 
按照上面的規則,對如下幾個例子進行分析: 
例1: 

1\struct DATA1 
2\\\
3\    char    c1; //偏移量0,累積size = 1 
4\    char    c2; //偏移量1,累積size = 1 + 1 = 2 
5\    short    si; //偏移量2,累積size = 2 + 2 
6\}; 


這個例子中,首先找到結構體變數的首地址,按照規則1,該首地址需要能被最寬基本型別成員整除,即能被short(位元組數2)整除。接下來,為c1分配記憶體,由於其佔一個位元組,此時記憶體狀態為*,然後,為c2分配記憶體,由於結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,而c2是char型成員,佔用一個位元組,剛好能夠對齊,此時記憶體狀態為**,最後,為short型成員分配記憶體,由於前兩個成員佔2個位元組,si相對於結構體首地址的偏移量剛好是2的整數倍,所以,此時記憶體狀態為**** 

例2:

1\struct DATA2 
2\\\
3\    char    c1; //偏移量0,累積size = 1 

4\    short    si; //偏移量1 + (1),累積size = 1 + (1) + 2 = 4 
5\    char    c2; //偏移量4,累積size = 4 + 1 = 5,但按最大長度sizeof(short) = 2對齊,故最後取6 
6\}; 

這個例子中,首先找到結構體變數的首地址,與上例同。接下來,為c1分配記憶體,由於其佔一個位元組,此時記憶體狀態為*,然後,為si分配記憶體,由於結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,而si是short型成員,佔用2個位元組,而前一個成員佔用一個位元組,所以需要在c1與si之間補一個位元組進行對齊,此時記憶體狀態為*×**,最後,為char型成員分配記憶體,由於前兩個成員佔4個位元組,c2相對於結構體首地址的偏移量是1的整數倍,所以,此時記憶體狀態為*×***,接下來,按照規則3,結構體的總大小為結構體最寬基本型別成員大小的整數倍,所以總大小應該是short(2位元組)型變數大小的整數倍,需要再補一個位元組,即*×***×,總共佔6個位元組 

例3:

1\struct DATA3 
2{ 
3\    char    c1; //偏移量0,累積size = 1 
4\    double    d; //偏移量1 + (7),累積size = 1 + (7) + 8 = 16 
5\    char    c2; //偏移量16,累積size = 16 + 1 = 17,但按最大長度sizeof(double) = 8對齊,故最後取24 
6\}; 


關於#pragma pack(n) 
VC中提供了#pragma pack(n)來設定變數以n位元組對齊方式。n位元組對齊就是說變數存放的起始地址的偏移量有兩種情況:第一、如果n大於等於該變數所佔用的位元組數,那麼偏移量必須滿足預設的對齊方式,第二、如果n小於該變數的型別所佔用的位元組數,那麼偏移量為n的倍數,不用滿足預設的對齊方式。結構的總大小也有個約束條件,分下面兩種情況:如果n大於所有成員變數型別所佔用的位元組數,那麼結構的總大小必須為佔用空間最大的變數佔用的空間數的倍數;否則必須為n的倍數。 
例4: 
1\#pragma pack(push) //儲存對齊狀態 
2\#pragma pack(2)//設定為2位元組對齊 
3\struct DATA4 
4{ 
5\    char    c1; 
6\    double  d1; 
7\    int    i1; 
8\    short  s1; 
9\}; 
10\#pragma pack(pop)//恢復對齊狀態 
以上結構的大小為16,下面分析其儲存情況,首先為c1分配空間,其偏移量為0,c1佔用1個位元組,由於1<2,所以按照char型別變數預設的對其方式。接著開始為d分配空間,這時其偏移量為1,需要補足1個位元組,這樣使偏移量滿足為n=2的倍數(因為sizeof(double)大於n),d佔用8個位元組。接著為i1分配空間,這時其偏移量為10,滿足為2的倍數,i1佔用4個位元組。這時已經為所有成員變數分配了空間,共分配了14個位元組。 接下來再為s1分配空間,由於min(sizeof(s1),2)=2,所以s1按照2位元組對齊,此時已經是對其的,所以分配2位元組記憶體,這時,總共佔用16位元組 

例5:

1\#pragma pack(push) //儲存對齊狀態 
2\#pragma pack(4)//設定為4位元組對齊 
3\struct DATA4 
4\\\
5\    char    c1; 
6\    double  d; 
7\    int    i1; 
8\    short  s1; 
9\}; 
10\#pragma pack(pop)//恢復對齊狀態 
以上結構的大小為20,下面分析其儲存情況,首先為c1分配空間,其偏移量為0,c1佔用1個位元組,由於1<4,所以按照char型別變數預設的對其方式。接著開始為d分配空間,這時其偏移量為1,需要補足3個位元組,這樣使偏移量滿足為n=4的倍數(因為sizeof(double)大於n),d佔用8個位元組。接著為i1分配空間,這時其偏移量為12,滿足為4的倍數,i1佔用4個位元組。這時已經為所有成員變數分配了空間,共分配了16個位元組。接下來再為s1分配空間,由於min(sizeof(s1),4)=2,所以s1按照2位元組對齊,此時已經是對齊的,所以分配2位元組記憶體,這時,總共佔用18位元組,由於佔用記憶體總數需要為4的整數倍,所以結構大小為20