天天看點

結構體長度問題

很久之前,我以為結構體的長度就是各個成員的長度之和,對此深信不疑,在有限的經曆中,這似乎也沒有出現過什麼問題。直到有一天,使用sizeof函數求一個結構體的長度時,意外地發現結構體的長度居然大于各成員長度之和。查閱資料才知道,原來編譯器為了加快資料存取速度,預設情況下會對結構體本身和其中的成員的儲存位置進行調整,以達到位元組對齊。是以,結構體的長度是大于等于各成員長度之和的。我恍然大悟,以為找到了真理。秉持着這樣的“真理”過了好長一段時間,相安無事。直有一次,我再次用sizeof求一個windows.h頭檔案下的一個結構體時,發現該結構體的長度剛好等于各成員的長度之和,而按照位元組對齊的規則配置設定結構體記憶體空間的話,該結構體的長度應該大于各成員長度之和才對。我有迷茫了,到底是怎麼回事?心想大概是sizeof函數出了點差錯什麼的。然而,最緻命的一擊是,我發現有些結構體,如果用sizeof求其長度,得到的結構體長度居然小于各成員的長度之和!甚是詭異,人生觀就這樣崩潰了!

遭遇了幾次挫折之後,我覺得我沒有完全了解結構體,于是花了幾天時間,查閱很多關于結構體方面的資料,深刻剖析了結構體的結構。下面是一些關于結構體長度計算的總結。

結構體的長度與結構體占用空間

結構體的長度與結構體占用的空間是兩個不同的概念,雖然有時候可以混用,但并不等同。結構體的長度是各成員長度之和,這句話本身并沒有什麼問題。關鍵是,系統為結構體配置設定了多少空間。用sizeof求結構體的長度,這種方法并不可靠,因為sizeof函數是用來求結構體占用空間的大小,而不是長度。一般情況下,結構體的實際占有空間的大小是等于結構體長度的,但也有例外。從實際的情況來看,用sizeof求結構體的占有空間,結果可能是剛好等于各成員大小之和,也可能是大于各成員大小之和,甚至是小于各成員大小之和的。下面,将詳細探讨結構體占用空間的問題。

1、結構體占據空間大小與位元組對齊

為了加快資料存取的速度,編譯器預設情況下會對結構體成員和結構體本身(實際上其他資料對象也是如此)存儲位置進行處理,使其存放的起始位址是一定位元組數的倍數,而不是順序存放,稱為位元組對齊。位元組對齊的規則為:

(1)結構體變量的首位址能夠被其最寬基本類型成員的大小所整除;

(2)結構體每個成員相對于結構體首位址的偏移量都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組;

(3)結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充位元組。

2、結構體占用空間的計算方法

結構體占用的空間跟成員的資料類型、成員的數目、還有成員的排列順序有關。

(1)成員的資料類型都相同時:

數組由多個相同資料類型的元素組成,一個數組成員可視為多個普通成員。

結構體占用空間=成員個數×成員資料類型的長度。
           

(2)成員的資料類型不相同時:

如果結構體中含有結構體成員,則要先按照位元組對齊原則對結構體成員的長度進行計算,然後再把結構體成員拆解,其成員的順序不變。

a.分析各個成員長度;
b.找出最大長度的成員長度L(結構體的長度一定是該成員的整數倍);
c.并按最大成員長度出現的位置将結構體分為若幹部分;
d.各個部分長度一次相加,求出大于該和的最小L的整數倍即為該部分占用的空間
e.将各個部分長度相加之和即為結構體總的占用空間。
           

(3)特殊結構體的占用空間計算

a.對于一個沒有任何成員的結構體,它占用的空間為1位元組,而不是0。
b.如果一個結構體中含有靜态的成員,那麼用sizeof函數算出來的結構體占用空間可能會比實際長度小。這是由于靜态成員儲存在資料段,
而sizeof函數隻會統計在棧中的成員。
           

3、例子

(1)空結構體

struct A{
}
           

空結構體也占用空間,大小為1,故sizeof(A)=1。

(2)成員資料類型相同

struct B{
    int x;
    int y;
}
struct C{
    int x;
    int a[];
    int y;
}
           

成員資料類型相同時,結構體的占用等于各成員長度之和。即sizeof(B)=4+4=8;sizeof(C)=4+4×4+4=24。

(3)成員的資料類型不同

成員的資料類型不同時,結構體的占用空間不但跟資料類型、成員數目有關,還跟成員的排列次序有關。

a.

struct D{
    char c;
    char s;
    int x;
}
           

x成員的長度最大,是以要以它作為基準進行位元組對齊。該結構體在記憶體中的布局為:

|char|char|---|---|-----int-----|
           

是以sizeof(D)=1+1+2+4=8。

b.

struct E{
    char c;
    int x;
    char s;
}
           

結構體E的成員數目和類型都與結構體D相同,不同的是成員的順序。該結構在記憶體中的布局如下:

|char|---|---|---|-----int----|char|---|---|---|
           

是以sizeof(E)=1+3+4+1+3=12。

c.

struct F{
    char c;
    int x;
    double y;
}
           

結構體F的記憶體布局如下:

|char|-----int----|---|---|---|-----------double---------|
           

sizeof(F)=1+4+3+8=15。

d.

struct G{
    char c;
    struct E e;
}
           

該結構體含有一個結構體成員e,上面已經計算出結構體E的大小為12位元組,E中的最長的成員類型是int型。該結構體在記憶體總的布局為:

|char|--|--|--|------struct E------|
即|char|--|--|--|char|--|--|--|---int---|char|--|--|--|
           

sizeof(G)=1+3+12=16。

e.

struct H{
    int x;
    stactic int y;
}
           

該結構體含有靜态的成員,靜态成員不在棧中,是以sizeof函數是不會計入靜态成員的長度的。sizeof(H)=4;

如何讓結構體的長度與占用的空間等同?

結構體的長度與其占用的空間并不等同,但有時候不得不通過求結構占用空間來獲得結構體的長度。如果求結構體的占用空間,直接用sizeof函數就可以了。而如果求結構體的長度,最好就是直接計算各成員的長度之和,這樣絕對不會錯。但有時候,這樣做會很麻煩,特别是結構體的成員比較多的時候。這時,我更希望用sizeof函數來求結構體的長度。

結構體的長度與占用空間之是以不等,是因為位元組對齊惹的禍。編譯器預設的位元組對齊是按照最寬的成員資料類型為基準的,既然有預設,那麼是否有自定義呢?答案是肯定的。如果将結構體位元組對齊的位元組數設定為1位元組,那麼結構體的長度與占用空間就一緻了。這就是為什麼對Windows.h頭檔案裡面的結構體使用sizeof函數時,得出的結果與結構體各成員長度之和剛好相等。

在VC或者VS編譯器中,使用預處理指令#pragma pack(n)可以設定對齊位元組數n(n=1,2,4,8,16)。例如:
           
//結構體A采用預設的位元組對齊
struct A{
    char c;
    int  x;
};

#pragma pack(push)//儲存位元組對齊數
#pragma pack(1)//設定對齊位元組數為1
struct B{
    char c;//1位元組
    int  x;//4位元組
};
#pragma pack(pop)//恢複對齊位元組數
           

結構體A與結構體B的結構完全一樣,結構體A采用預設的位元組對齊,故sizeof(A)=8;而結構體B将位元組對齊的位元組數設為1,這時結構體的占用空間剛好等于結構體各成員長度之和,故sizeof(B)=5。