struct對齊是一個老話題了,一直都沒怎麼弄懂,在網上找了很久,看了相關的理論和事例,終於弄明白了。
一、什麼是struct對齊。
現代計算機中內存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址訪問,這就需要各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
上面說得很理論,其實,說白了,就是struct的size不是有子變量的size加起來的,而是有一定的規則,這個規則就叫做struct對齊 。
二、爲什麼要對齊。
對齊的作用和原因:各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。比如有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設爲32位系統)如果存放在偶地址開始的地方,那麼一個讀週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數據。顯然在讀取效率上下降很多。
用一句簡單的話說就是兩個原因,第一,適配不同的CPU;第二,加快對數據的讀寫速度(即用空間換時間)。
三、對齊規則
(1)每個成員分別按自己的對齊字節數和PPB(指定的對齊字節數)兩個字節數最小的那個對齊,並能最小化長度。
(2)複雜類型(如結構)的默認對其方式是它最長的成員的對齊方式,這樣在成員是複雜類型時,可以最小化長度。
(3)結構體對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊。
PPB(指定的對齊字節數)名字解釋:
比如:
#pragma pack (2)
struct B{
char b;
int a;
short c;
};
#pragma pack ()
這裏的#pragma pack就表示指定對齊字節數,如上面就表示指定了2,而最後一句表示取消指定,採用系統默認的指定對齊數(不同的系統默認不一樣,故下面的例子統一採用指定對齊字節數)。
另外,在32位的機器上,各個基本類型的size如下:
char: 1 (有符號無符號同)
short: 2 (有符號無符號同)
int: 4 (有符號無符號同)
long: 4 (有符號無符號同)
float: 4
double: 8
指針: 8
下面就不詳細說明了。
下面所有的例子都假設存儲位置是從0開始的。
四、舉例計算
#pragma pack (2)
struct A
{
char b;
int a;
short c;
};
#pragma pack ()
可以看出來PPB是2
根據規則(1)b的對齊字節數是min(sizeof(char),PPB)=min(1,2)=1,所以,b所佔的存儲位置是0;a的對齊字節數是min(4,2)=2,a所佔的存儲位置是[2,5],爲什麼從2開始呢,因爲2%min(4,2)==0,而1%min(4,2)不等於0;c的對齊字節數是min(2,2)=2,起所佔存儲位置是[6,7]。如下:
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
佔用變量 | b | a | a | a | a | c | c | |
佔用原因 | 0%1=0 | 2%2=0 | 6%2=0 |
這樣,總大小就是8了。但這沒完,我們還要看一下規則(3).
根據規則(3),struct A字節對齊數最大是2,而8%2==0,故,符合規則(3),sizeof(A)是8。
#pragma pack (4)
struct B
{
char b;
int a;
short c;
};
#pragma pack ()
和A是不是很像?是的,但是這裏的PPB是4。我們還是一點一點分析吧。
根據規則(1)b的對齊字節數是min(1,4)=1,故佔內存位置爲[0,0];a的對齊字節數是min(4,4)=4,故佔內存位置是[4,7];c的對齊字節數是min(2,4)=2,故佔內存位置是[8,9]。如下表:
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
佔用變量 | b | a | a | a | a | c | c | |||
佔用原因 | 0%1=0 | 4%4=0 | 8%2=0 |
這裏,總大小是10,最大對齊字節數是4.所以,根據規則(3),10%4 != 0,需要再加兩個字節,即12。所以,最後sizeof(B)=12。
#pragma pack (4)
struct C
{
char a;
struct B t;
int b;
short c;
};
#pragma pack ()
我們上面知道,在對齊字節數爲4時,sizeof(B)=12,最大對齊字節數是4,所以根據規則(2),t本身的對齊字節數是4
根據規則(1),下面直接列表:
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
佔用變量 | a | t | t | t | t | t | t | t | t | t | t | t | t | b | b | b | b | c | c | |||
佔用原因 | 0%1=0 | 4%4=0 | 16%4=0 | 20%2=0 |
例四、
#pragma pack (4)
struct D{
char a;
struct A t;
int b;
short c;
};
#pragma pack()
例四和例三有什麼區別呢?很顯然,例三中B的指定對齊字節數是4,而例四中指定對齊字節數是2。這個時候該怎麼辦?
這時候,我們要記住一個原則,在哪個指定區域定義的,struct的指定對齊字節數就是哪個。我們知道,A在指定字節數爲2中,根據規則(2),A本身的對齊字節數是4,而且sizeof(A)=8
根據規則(1),下面直接列表:
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
佔用變量 | a | t | t | t | t | t | t | t | t | b | b | b | b | c | c | |||
佔用原因 | 0%1=0 | 4%4=0 | 12%4=0 | 16%2=0 |
根據規則(3),最大的對齊字節數是4,而18%4=2,故sizeof(D)=20
例五、
#pragma pack(4)
struct E{
short u[5];
int b;
short c;
};
#pragma pack()
這裏面有一個數組。
數組,我們可以看作複雜類型,他本身的最齊字節數是單位本身對齊字節數。所以short u[5]本身的字節對齊數是short的字節對齊數2。而sizeof(u)則是10.
根據規則(1),下面直接列表。
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
佔用變量 | u | u | u | u | u | u | u | u | u | u | b | b | b | b | c | c | ||
佔用原因 | 0%2=0 | 12%4=0 | 16%2=0 |
根據規則(3),所以sizeof(E)=20
例六、
#pragma pack(4)
struct F{
char a;
short *pb;
int b;
short c;
};
#pragma pack()
這裏面有一個指針,根據上面規定指針本身的對齊字節數是8,大小也是8。所以short *pb的本身對齊字節數是8,而sizeof(pb)=8。
根據規則(1),下面直接列表。
內存位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
佔用變量 | a | pb | pb | pb | pb | pb | pb | pb | pb | b | b | b | b | c | c | |||
佔用原因 | 0%1=0 | 4%(min(4,8))=0 | 12%4=0 | 16%2=0 |
根據規則(3),所以sizeof(F)=20
1.在編寫c程序的時候可以通過改變結構體內變量的順序或其他來減少存儲空間的浪費。
2.凡是c面試幾乎都會提到相關的問題,務必掌握!
參考文獻: