struct A{ int a;//默認對齊模數k(int)=sizeof(int)=4 char b;//默認對齊模數k(char)=sizeof(char)=1 struct B d;//默認對齊模數k(struct B)爲B成員最大的對齊模數即k(double)=8 };//A默認對齊模數k(struct A)爲A成員最大的對齊模數k(struct B)=8 struct B{ int a;//默認對齊模數k(int)=sizeof(int)=4 char b;//默認對齊模數k(char)=sizeof(char)=1 double c;//默認對齊模數k(double)=sizeof(double)=8 short ;//默認對齊模數k(short)=sizeof(short)=2 };//B默認對齊模數k(struct B)爲B成員最大的對齊模數即k(double)=8
由上面的兩個結構體能夠看出 :c++
基本數據類型的默認對齊模數k=sizeof(type);程序員
複雜數據類型如結構體,其默認對齊模數k=max{各個成員的默認對齊模數};編程
存儲規則:數組
1.當系統指定了對齊模數k時,不一樣類型的數據按照k(m)=min{默認的對齊模數k,系統指定的k}模數來存儲數據;數據結構
2.該類型的數據起始地址add%k(m)=0;爲何要這樣知足這個條件呢?架構
其緣由是各個硬件平臺對存儲空間的處理上有很大的不一樣。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。好比有些平臺每次讀都是從偶地址開始,若是一個int型(假設爲32位系統)若是存放在偶地址開始的地方,那 麼一個讀週期就能夠讀出這32bit,而若是存放在奇地址開始的地方,就須要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能獲得該32bit數 據。顯然在讀取效率上降低不少。函數
3.該類型的數據大小size%k(m)=0,不足的要補齊(結構體中常常須要補齊);爲何要這樣知足這個條件呢?佈局
由於考慮到該類型的數組,按照數組的定義,數組中全部元素都是緊挨着的。性能
因爲程序運行時佔用的內存過大,因此想辦法給程序瘦身。
在調試中發現結構體佔用的size居然和預想的不同,原來……編碼
看看下面講的吧,確定會不枉此看哦!
1,好比:
struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
sizeof(A)=6, sizeof(B)=8,爲何?
注:sizeof(short)=2,sizeof(long)=4
由於:「成員對齊有一個重要的條件,即每一個成員按本身的方式對齊.其對齊的規則是,每一個成員按其類型的對齊參數(一般是這個類型的大小)和指定對齊參數(這裏默認是8字節)中較小的一個對齊.而且結構的長度必須爲所用過的全部對齊參數的整數倍,不夠就補空字節.」(引用)
結構體A中有3個short類型變量,各自以2字節對齊,結構體對齊參數按默認的8字節對齊,則a1,a2,a3都取2字節對齊,則sizeof(A)爲6,其也是2的整數倍;
B中a1爲4字節對齊,a2爲2字節對齊,結構體默認對齊參數爲8,則a1取4字節對齊,a2取2字節對齊,結構體大小6字節,6不爲4的整數倍,補空字節,增到8時,符合全部條件,則sizeof(B)爲8;
能夠設置成對齊的
#pragma pack(1)
#pragma pack(push)
#pragma pack(1)
struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
#pragma pack(pop)
結果爲sizeof( A)=6,sizeof( B)=6
************************
2,又如:
#pragma pack(8)
struct S1{
char a;
long b;
};
struct S2 {
char c;
struct S1 d;
long long e;
};
#pragma pack()
sizeof(S2)結果爲24.
成員對齊有一個重要的條件,即每一個成員分別對齊.即每一個成員按本身的方式對齊.
也就是說上面雖然指定了按8字節對齊,但並非全部的成員都是以8字節對齊.其對齊的規則是,每一個成員按其類型的對齊參數(一般是這個類型的大小)和指定對齊參數(這裏是8字節)中較小的一個對齊.而且結構的長度必須爲所用過的全部對齊參數的整數倍,不夠就補空字節.
S1中,成員a是1字節默認按1字節對齊,指定對齊參數爲8,這兩個值中取1,a按1字節對齊;成員b是4個字節,默認是按4字節對齊,這時就按4字節對齊,因此sizeof(S1)應該爲8;
S2 中,c和S1中的a同樣,按1字節對齊,而d 是個結構,它是8個字節,它按什麼對齊呢?對於結構來講,它的默認對齊方式就是它的全部成員使用的對齊參數中最大的一個,S1的就是4.因此,成員d就是按4字節對齊.成員e是8個字節,它是默認按8字節對齊,和指定的同樣,因此它對到8字節的邊界上,這時, 已經使用了12個字節了,因此又添加了4個字節的空,從第16個字節開始放置成員e.這時,長度爲24,已經能夠被8(成員e按8字節對齊)整除.這樣, 一共使用了24個字節.
a b
S1的內存佈局:1***, 1111,
c S1.a S1.b e
S2的內存佈局:1***, 1***, 1111, ****11111111
這裏有三點很重要:
1.每一個成員分別按本身的方式對齊,並能最小化長度
2.複雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是複雜類型時,能夠最小化長度
3.對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時能夠保證每一項都邊界對齊
補充一下,對於數組,好比:
char a[3];這種,它的對齊方式和分別寫3個char是同樣的.也就是說它仍是按1個字節對齊.
若是寫: typedef char Array3[3];
Array3這種類型的對齊方式仍是按1個字節對齊,而不是按它的長度.
不論類型是什麼,對齊的邊界必定是1,2,4,8,16,32,64....中的一個.
字節對齊詳解
一.爲何要對齊?
現代計算機中內存空間都是按照byte劃分的,從理論上講彷佛對任何類型的變量的訪問能夠從任何地址開始,但實際狀況是在訪問特定類型變量的時候常常在特 定的內存地址訪問,這就須要各類類型數據按照必定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
對齊的做用和緣由:各個硬件平臺對存儲空間的處理上有很大的不一樣。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。好比有些架構的CPU在訪問 一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其餘平臺可能沒有這種狀況,可是最多見的是若是不按照適合其平臺要求對 數據存放進行對齊,會在存取效率上帶來損失。好比有些平臺每次讀都是從偶地址開始,若是一個int型(假設爲32位系統)若是存放在偶地址開始的地方,那 麼一個讀週期就能夠讀出這32bit,而若是存放在奇地址開始的地方,就須要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能獲得該32bit數 據。顯然在讀取效率上降低不少。
二.字節對齊對程序的影響:
先讓咱們看幾個例子吧(32bit,x86環境,gcc編譯器):
設結構體以下定義:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
如今已知32位機器上各類數據類型的長度以下:
char:1(有符號無符號同)
short:2(有符號無符號同)
int:4(有符號無符號同)
long:4(有符號無符號同)
float:4 double:8
那麼上面兩個結構大小如何呢?
結果是:
sizeof(strcut A)值爲8
sizeof(struct B)的值倒是12
結構體A中包含了4字節長度的int一個,1字節長度的char一個和2字節長度的short型數據一個,B也同樣;按理說A,B大小應該都是7字節。
之因此出現上面的結果是由於編譯器要對數據成員在空間上進行對齊。上面是按照編譯器的默認設置進行對齊的結果,那麼咱們是否是能夠改變編譯器的這種默認對齊設置呢,固然能夠.例如:
#pragma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct C)值是8。
修改對齊值爲1:
#pragma pack (1) /*指定按1字節對齊*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct D)值爲7。
後面咱們再講解#pragma pack()的做用.
三.編譯器是按照什麼樣的原則進行對齊的?
先讓咱們看四個重要的基本概念:
1.數據類型自身的對齊值:
對於char型數據,其自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,單位字節。
2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack (value)時的指定對齊值value。
4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
有 了這些值,咱們就能夠很方便的來討論具體數據結構的成員和其自身的對齊方式。有效對齊值N是最終用來決定數據存放地址方式的值,最重要。有效對齊N,就是 表示「對齊在N上」,也就是說該數據的"存放起始地址%N=0".而數據結構中的數據變量都是按定義的前後順序來排放的。第一個數據變量的起始地址就是數 據結構的起始地址。結構體的成員變量要對齊排放,結構體自己也要根據自身的有效對齊值圓整(就是結構體成員變量佔用總長度須要是對結構體有效對齊值的整數 倍,結合下面例子理解)。這樣就不能理解上面的幾個例子的值了。
例子分析:
分析例子B;
struct B
{
char b;
int a;
short c;
};
假 設B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值默認爲4。第一個成員變量b的自身對齊值是1,比指定或者默認指定 對齊值4小,因此其有效對齊值爲1,因此其存放地址0x0000符合0x0000%1=0.第二個成員變量a,其自身對齊值爲4,因此有效對齊值也爲4, 因此只能存放在起始地址爲0x0004到0x0007這四個連續的字節空間中,複覈0x0004%4=0,且緊靠第一個變量。第三個變量c,自身對齊值爲 2,因此有效對齊值也是2,能夠存放在0x0008到0x0009這兩個字節空間中,符合0x0008%2=0。因此從0x0000到0x0009存放的 都是B內容。再看數據結構B的自身對齊值爲其變量中最大對齊值(這裏是b)因此就是4,因此結構體的有效對齊值也是4。根據結構體圓整的要求, 0x0009到0x0000=10字節,(10+2)%4=0。因此0x0000A到0x000B也爲結構體B所佔用。故B從0x0000到0x000B 共有12個字節,sizeof(struct B)=12;其實若是就這一個就來講它已將知足字節對齊了, 由於它的起始地址是0,所以確定是對齊的,之因此在後面補充2個字節,是由於編譯器爲了實現結構數組的存取效率,試想若是咱們定義了一個結構B的數組,那 麼第一個結構起始地址是0沒有問題,可是第二個結構呢?按照數組的定義,數組中全部元素都是緊挨着的,若是咱們不把結構的大小補充爲4的整數倍,那麼下一 個結構的起始地址將是0x0000A,這顯然不能知足結構的地址對齊了,所以咱們要把結構補充成有效對齊大小的整數倍.其實諸如:對於char型數據,其 自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,這些已有類型的自身對齊值也是基於數組考慮的,只 是由於這些類型的長度已知了,因此他們的自身對齊值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
第 一個變量b的自身對齊值爲1,指定對齊值爲2,因此,其有效對齊值爲1,假設C從0x0000開始,那麼b存放在0x0000,符合0x0000%1= 0;第二個變量,自身對齊值爲4,指定對齊值爲2,因此有效對齊值爲2,因此順序存放在0x000二、0x000三、0x000四、0x0005四個連續 字節中,符合0x0002%2=0。第三個變量c的自身對齊值爲2,因此有效對齊值爲2,順序存放
在0x000六、0x0007中,符合 0x0006%2=0。因此從0x0000到0x00007共八字節存放的是C的變量。又C的自身對齊值爲4,因此C的有效對齊值爲2。又8%2=0,C 只佔用0x0000到0x0007的八個字節。因此sizeof(struct C)=8.
四.如何修改編譯器的默認對齊值?
1.在VC IDE中,能夠這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認是8字節。
2.在編碼時,能夠這樣動態修改:#pragma pack .注意:是pragma而不是progma.
五.針對字節對齊,咱們在編程中如何考慮?
若是在編程的時候要考慮節約空間的話,那麼咱們只須要假定結構的首地址是0,而後各個變量按照上面的原則進行排列便可,基本的原則就是把結構中的變量按照 類型大小從小到大聲明,儘可能減小中間的填補空間.還有一種就是爲了以空間換取時間的效率,咱們顯示的進行填補空間進行對齊,好比:有一種使用空間換時間作 法是顯式的插入reserved成員:
struct A{
char a;
char reserved[3];//使用空間換時間
int b;
}
reserved成員對咱們的程序沒有什麼意義,它只是起到填補空間以達到字節對齊的目的,固然即便不加這個成員一般編譯器也會給咱們自動填補對齊,咱們本身加上它只是起到顯式的提醒做用.