您好,登錄后才能下訂單哦!
之前對結構體的字節對齊一直是一知半解,今天看了百度百科以及大家的博客后,總算是搞清楚了字節對齊到底是怎么一回事,非常感謝大家的分享。對此,將我的所看和所想分享給大家(希望可以幫上那些和我之前一樣對結構體字節對齊不是很清晰的小伙伴),還有希望大牛們對我的小見解給予意見與建議。
首先,先來介紹什么叫字節對齊?百科上是這么講的:現代計算機中內存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址訪問(即不同架構的cpu為了提高訪問內存的速度),這就需要各種類型數據按照“一定的規則”在空間上排列,而不是順序的一個接一個排放,這就是對齊。那么,這種規則是?:一個變量占用 n 個字節,則該變量的其實地址必須能夠被 n 整除,即:存放起始地址 % n = 0,對于結構體而言,這個 n 取其成員中的數據類型占空間的值最大的那個。舉個例子,比如有些平臺訪問內存地址都是從偶數地址開始,對于一個int型(假設32位系統),如果從偶數地址開始的地方存放,這樣一個周期就可以讀出這個int數據,但是如果從奇數地址開始的地址存放,就需要讀兩個周期,并對兩次讀出的結果的高低字節進行拼湊才能得到這個int數據,這樣明顯降低了讀取的效率。
怎樣進行字節對齊?:每個成員按其類型的對其參數(通常是這個類型的大小)和制定對其參數(不指定則取默認值)中較小的一個對齊,并且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節。即:
1、數據類型的自身對齊值: char: 1, short: 2, int、long、float: 4, double: 8。(單位為字節)
2、結構體自身對齊值:其成員中自身對齊值最大的那個值。
若指定對齊值value: #pragma pack(value)
未指定則取默認值。
針對數據類型的成員,它僅有一個對齊參數,其本身的長度(是這個對齊參數的1倍)。對于結構體而言,它使用了多種數據類型。意思即就是:每個成員的起始地址 % 自身對齊值 = 0,如果不等于0則先補空字節直至這個表達式成立。即如下規則:
1、每個成員的起始地址 % 每個成員的自身對齊值(不是結構體的自身對齊值)=0,如果不等于0則先補空字節直至這個表達式成立。
2、結構體的長度必須為結構體的自身對齊值的整數倍,不夠就補空字節。
不同平臺下對齊系數與示例:
每個特定平臺的編譯器都有一個默認的對齊系數,gcc中是4,VC中是8.也可以通過預編譯命令#pragma pack(value)來指定該系數,經測試gcc中的value值只能是1,2,4.
舉個例子:
#pragma pack(8)
struct A{
char a;
long b;
};
struct B{
char a;
struct A b;
long c;
};
struct C{
char a;
struct A b;
double c;
};
struct D{
char a;
struct A b;
double c;
int d;
};
struct E{
char a;
int b;
struct A c;
double d;
};
在VC中,
1、對于struct A,char類型自身對齊值為1,long類型自身對齊值為4,結構體的自身對齊值取其成員最大的對齊值,即大小4.那么struct A在內存中的順序步驟為:
(1)、char a,地址范圍為0x0000~0x0000,起始地址為0x0000,滿足0x0000%1 = 0,這個成員字節已經對齊。
(2)、long b,地址起始位置不能從0x0001開始,因為0x0001%4 !=0,所以先補空字節,直到0x0003結束 ,即補3個空字節,從0x0004開始存放b,其地址范圍為0x0004~0x0007.
(3)、此時成員都存放結束,結構體長度為8,為結構體自身對齊值的2倍。
struct A中各成員在內存中的位置為:a*** b,sizeof(struct A) = 8.(每個*代表一位,成員各自代表自己所占的位,比如a占一位,b占4位)
2、對于struct B,里面有個類型為struct A的成員 b自身對齊值為4,對于long 類型,其自身對齊值為4。故struct B的自身對齊值為4.那么struct B在內存中的順序步驟為:
(1)、char a,地址范圍為0x0000~0x0000,起始地址為0x0000,滿足0x0000%1 = 0,這個成員字節已經對齊。
(2)、struct A b,地址起始位置不能從0x0001開始,因為0x0001%4 != 0,所以先補空字節,直到0x0003結束,即補3個空字節,從0x0004開始存放b,其地址范圍為0x0004~0x0011.
(3)、long c,地址起始位置從0x0012開始,因為0x0012%4 = 0,其地址范圍為0x0012~0x0015.
(4)、此時成員都存放結束,結構體長度為16,為結構體自身對齊值的4倍。
struct B中各成員在內存中的位置為:a*** b c,sizeof(struct B) = 16.(每個*代表一位,成員各自代表自己所占的位,比如a占一位,b占8位,c占四位)
3、對于struct C,里面有個類型為struct A的成員b自身對齊值為4,對于double類型,其自身對齊值為8.故struct C的自身對齊值為8.那么struct C在內存中的順序步驟為:
(1)、char a,地址范圍為0x0000~0x0000,起始地址為0x0000,滿足0x0000%1 = 0,這個成員字節已經對齊。
(2)、struct A b,地址起始位置不能從0x0001開始,因為0x0001%4 !=0,所以先補空字節,直到0x0003結束,即補3個空字節,從0x0004開始存放b,其地址范圍為0x0004~0x0011.
(3)、double c,地址起始位置不能從0x0012開始,因為0x0012%8 != 0,所以先補空字節,直到0x0015結束,即補4個空字節,從0x0016開始存放c,其地址范圍為0x0016~0x0023.
(4)、此時成員都存放結束,結構體長度為24,為結構體自身對齊值的3倍。
struct C中各成員在內存中的位置為:a*** b**** c,sizeof(struct C)=24。(每個星號代表一位,成員各自代表自己所占的位,比如a占1位,b占8位,c占8位)
4、對于struct D,前面的三個成員與struct C是一致的。對于第四成員d,因為0x0024%4 == 0,所以可以從0x0024開始存放d,其地址范圍為0x0024~0x0027。此時成員都存放結束,結構體長度為28,28不是結構體自身對齊值8的倍數,所以要在后面補四個空格,即在0x0028~0x0031上補四個空格。則結構體長度為32,為結構體自身對齊值的4倍。
struct D中各成員在內存中的位置為:a*** b**** c d****,sizeof(struct D) = 32。(每個星號代表一位,成員各自代表自己所占的位,比如a占一位,b占8位,c占8位,d占4位)
5、對于struct E,各成員在內存中的位置為:a*** b c d,sizeof(struct E) = 24。(每個星號代表一位,成員各自代表自己所占的位,比如a占1位,b占4位,c占8位,d占8位)
通過struct D和struct E可以看出,在成員數量和類型一致的情況下,后者所占的空間少于前者,因為后者的填充的字節要少。如果我們在編程時考慮節約空間的話,應該遵循將變量按照類型大小從小到大聲明的原則,這樣盡量減少填補空間。另外,可以在填充空字節的地方來插入reserved成員,例如: struct A{char a; char reserved[3]; int b;};
這樣做的目的主要是為了對程序員起一個提示作用,如果不加則編譯器會自動補齊。
在gcc中,
由于對齊系數最大只能為4,所以上述結構體占內存大小為:8,16,20,24,24。
舉例如下驗證gcc下的字節對齊:
1、默認情況(對齊系數)
struct st1{
char ch; //長度1<n,按1對齊,0%1 = 0,起始相對位置=0;存放區間[0]
int num; //長度4=n,按4對齊,4%4 = 0,起始相對位置=4;存放區間[4,7]
long lv; //長度4=n,按4對齊,8%4 = 0,起始相對位置=8;存放區間[8,11]
};
整個結構體成員對齊后所占的空間為[0,11],占12個字節,接著結構體本身對齊,成員中最長的是4,n也等于4,所以結構體本身按4字節對齊(即對齊系數)。整個結構體的大小=比整個結構體數據成員所占的總空間大或相等且和對齊系數求模結果為0、與之距離最近的數。
本例中,12%4 = 0,所以結構體st1占12個字節的空間。
2、#pragma pack(1)(即n=1)
struct st1{
char ch;//長度1=n,按1對齊,0%1=0,起始相對位置=0;存放區間[0]
int num;//長度4>n,按n對齊,1%1=0,起始相對位置=1;存放區間[1,4]
long lv;//長度4>n,按n對齊, 5%1=0,起始相對位置=5;存放區間[5,8]
};
整個結構體成員對齊后所占的區間為[0,8],占9個字節,接著結構體本身對齊,成員中最長的是4,n等于1,所以結構體本身按1對齊(即對齊系數)。
本例中,9%1 = 0,所以結構體st1占9個字節的空間。
3、#pragma pack(2)(即n=2)
struct st1{
char ch;//長度1<n,按1對齊,0%1=0,起始相對位置=0;存放區間[0]
int num;//長度4>n,按n對齊,2%2=0,起始相對位置=2;存放區間[2,5]
long lv;//長度4>n,按n對齊,6%2=0, 起始相對位置=6;存放區間[6,9]
};
整個結構體成員對齊后所占的區間為[0,9],占10個字節,接著結構體本身對齊,成員中最長的是4, n等于2,所以結構體本身按2對齊(即對齊系數)。
本例中,10%2 = 0,所以結構體st1占10個字節的空間。
為什么說#pragma pack(n)中n只能是1,2,4呢?
比如3,如果n=3,在編譯的時候會警告“對齊邊界必須是二的較小次方,而不是3”,也就是說這樣設置是沒有用的,按默認對齊系數對齊。
再如8,會有什么結果?看下例:
4、#pragma pack(8)(即n=8)
struct st1{
char ch;
long long num;
short n;
int n;
};
假設8起作用,分析如下:
struct st1{
char ch; //長度1<n,按1對齊,0%1=0,起始相對位置=0;存放區域[0]
long long num; //長度8=n, 按8對齊,8%8=0,起始相對位置=8;存放區域[8,15]
short n; //長度2<n, 按2對齊,16%2=0,起始相對位置=16;存放區域[16,17]
int n; //長度4<n, 按4對齊,20%4=0,起始相對位置=20;存放區域[20,23]
};
整個結構體成員對齊后所占的區間為[0,23],占24個字節,接著結構體本身對齊,成員中最長的是8,n=8,所以結構體本身按8對齊(即對齊系數)。24%8=0,所以占24個字節。
然而,很不幸,運行的結果是20.接下來用默認的對齊系數4來分析:
struct st1{
char ch; //長度1<4,按1對齊,0%1=0,起始相對位置=0;存放區域[0]
long long num; //長度8>4, 按4對齊,4%4=0,起始相對位置=4;存放區域[4,11]
short n; //長度2<4, 按2對齊,12%2=0,起始相對位置=12;存放區域[12,13]
int n; //長度4=4, 按4對齊,16%4=0,起始相對位置=16;存放區域[16,19]
};
整個結構體成員對齊后所占的區間為[0,19],占20個字節,接著結構體本身對齊,成員中最長的是8,n等于4所以結構體本身按4對齊(即對齊系數)。20%4=0,所以占20個字節。與運行結果一致。
綜上,當n=8的時候gcc仍然使用的是默認的對齊系數4.
位域
有些信息在存儲時,并不需要占用一個完整的字節,而只需占幾個或一個二進制位。例如在存放一個開關量是,只有0和1兩種狀態,用一位二進制位即可。為了節省存儲空間,并使處理簡便,c語言又提供了一種數據結構,稱為“位域”或“位段”。所謂“位域”是把一個字節中的二進制位劃分為幾個不同的區域,并說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。
位域定義與結構體定義相仿,其形式為:
struct 位域結構名
{位于列表};
其中位于列表的形式為:
類型說明符 位域名:位域長度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
如果結構體中含有位域(bit-filed),那么VC中準則又要有所更改:
1)如果相鄰位域字段的類型相同,且其位寬之和小于類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止;
2)如果相鄰位域字段的類型相同,但其位寬之和大于類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數倍;
3)如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC++6采取不壓縮方式(指不同位域字段存放在不同的位域類型字節中),Dev-C++和GCC都采取壓縮方式;
struct s1
{
int i:8;
char j:4;
int a:20;
double b;
};
struct s2
{
int i:8;
char j:4;
int a:21;
double b;
};
sizeof(struct s1), VC中為24,gcc中為12
sizeof(struct s2), VC中為24,gcc中為16
4)如果位域字段之間穿插著非位域字段,則不進行壓縮;
struct s
{
char c:2;
double i;
int a:4;
};
在GCC下占據的空間為16字節,在VC下占據的空間是24個字節。
5)整個結構體的總大小為最寬基本類型成員大小的整數倍。
注意:
對齊模數的選擇只能是根據基本數據類型,所以對于結構體中嵌套結構體,只能考慮其拆分的基本數據類型。而對于對其準則中的第二條,確實要將整個結構體看成是一個成員,成員大小按照該結構體根據對齊準則判斷所得的大小。
類對象在內存中存放的方式和結構體類似,這里就不再說明。需要指出的是,類對象的大小只是包括類中非靜態成員變量所占的空間,如果有虛函數,那么再另外增加一個指針所占的空間即可。
呼呼~~,終于寫完了,非常感謝參考大神的講解,自己寫完之后對這塊的知識又熟悉了很多,額。。。。以后還是要多鞏固鞏固,徹底改變自己以前錯誤的思想。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。