C++之字节对齐与结构体大小
說明:
結(jié)構(gòu)體的sizeof值,并不是簡單的將其中各元素所占字節(jié)相加,而是要考慮到存儲空間的字節(jié)對齊問題。這些問題在平時編程的時候也確實不怎么用到,但在一些筆試面試題目中出是常常出現(xiàn),
一、解釋
現(xiàn)代計算機中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。比如有些架構(gòu)的CPU在訪問一個沒有進(jìn)行對齊的變量的時候會發(fā)生錯誤,那么在這種架構(gòu)下編程必須保證字節(jié)對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對數(shù)據(jù)存放進(jìn)行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,并對兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)據(jù)。
二、準(zhǔn)則
其實字節(jié)對齊的細(xì)節(jié)和具體編譯器實現(xiàn)相關(guān),但一般而言,滿足三個準(zhǔn)則:
1. 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2. 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié);
3. 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)。
三、基本概念
字節(jié)對齊:計算機存儲系統(tǒng)中以Byte為單位存儲數(shù)據(jù),不同數(shù)據(jù)類型所占的空間不同,如:整型(int)數(shù)據(jù)占4個字節(jié),字符型(char)數(shù)據(jù)占一個字節(jié),短整型(short)數(shù)據(jù)占兩個字節(jié),等等。計算機為了快速的讀寫數(shù)據(jù),默認(rèn)情況下將數(shù)據(jù)存放在某個地址的起始位置,如:整型數(shù)據(jù)(int)默認(rèn)存儲在地址能被4整除的起始位置,字符型數(shù)據(jù)(char)可以存放在任何地址位置(被1整除),短整型(short)數(shù)據(jù)存儲在地址能被2整除的起始位置。這就是默認(rèn)字節(jié)對齊方式。
?四、結(jié)構(gòu)體長度求法
1.成員都相同時(或含數(shù)組且數(shù)組數(shù)據(jù)類型同結(jié)構(gòu)體其他成員數(shù)據(jù)類型):
結(jié)構(gòu)體長度=成員數(shù)據(jù)類型長度×成員個數(shù)(各成員長度之和);
結(jié)構(gòu)體中數(shù)組長度=數(shù)組數(shù)據(jù)類型長度×數(shù)組元素個數(shù);
2.成員不同且不含其它結(jié)構(gòu)體時;
(1).分析各個成員長度;
(2).找出最大長度的成員長度M(結(jié)構(gòu)體的長度一定是該成員的整數(shù)倍);
(3).并按最大成員長度出現(xiàn)的位置將結(jié)構(gòu)體分為若干部分;
(4).各個部分長度一次相加,求出大于該和的最小M的整數(shù)倍即為該部分長度
(5).將各個部分長度相加之和即為結(jié)構(gòu)體長度
3.含有其他結(jié)構(gòu)體時:
(1).分析各個成員長度;
(2).對是結(jié)構(gòu)體的成員,其長度按b來分析,且不會隨著位置的變化而變化;
(3).分析各個成員的長度(成員為結(jié)構(gòu)體的分析其成員長度),求出最大值;
(4).若長度最大成員在為結(jié)構(gòu)體的成員中,則按結(jié)構(gòu)體成員為分界點分界;
其他成員中有最大長度的成員,則該成員為分界點;
求出各段長度,求出大于該和的最小M的整數(shù)倍即為該部分長度
(5).將各個部分長度相加之和即為結(jié)構(gòu)體長度
?五、空結(jié)構(gòu)體
“空結(jié)構(gòu)體”(不含數(shù)據(jù)成員)的大小不為0,而是1。試想一個“不占空間”的變量如何被取地址、兩個不同的“空結(jié)構(gòu)體”變量又如何得以區(qū)分呢于是,“空結(jié)構(gòu)體”變量也得被存儲,這樣編譯器也就只能為其分配一個字節(jié)的空間用于占位了。
六、有static的結(jié)構(gòu)體
struct S4{ char a; long b; static long c; //靜態(tài) };靜態(tài)變量存放在全局?jǐn)?shù)據(jù)區(qū)內(nèi),而sizeof計算棧中分配的空間的大小,故不計算在內(nèi),S4的大小為4+4=8。
?七、舉例說明
1.舉例1
很顯然默認(rèn)對齊方式會浪費很多空間,例如如下結(jié)構(gòu):
本來只用了11bytes(5+4+2)的空間,但是由于int型默認(rèn)4字節(jié)對齊,存放在地址能被4整除的起始位置,即:如果name[5]從0開始存放,它占5bytes,而num則從第8(偏移量)個字節(jié)開始存放。所以sizeof(student)=16。于是中間空出幾個字節(jié)閑置著。但這樣便于計算機快速讀寫數(shù)據(jù),是一種以空間換取時間的方式。其數(shù)據(jù)對齊如下圖:
如果我們將結(jié)構(gòu)體中變量的順序改變?yōu)?#xff1a;
則,num從0開始存放,而name從第4(偏移量)個字節(jié)開始存放,連續(xù)5個字節(jié),score從第10(偏移量)開始存放,故sizeof(student)=12。其數(shù)據(jù)對齊如下圖:
如果我們將結(jié)構(gòu)體中變量的順序再次改為為:
則,sizeof(student)=12。其數(shù)據(jù)對齊如下圖:
2.舉例2
(1)
struct test1 { int a; int b[4]; };
sizeof(test1)=sizeof(int)+4*sizeof(int)=4+4*4=20;
(2)
struct test2 { char a; int b; double c; bool d; };?
分析:該結(jié)構(gòu)體最大長度double型,長度是8,因此結(jié)構(gòu)體長度分兩部分:
第一部分是a、 b、 c的長度和,長度分別為1,4,8,則該部分長度和為13,取8的大于13的最小倍數(shù)為16;
第二部分為d,長度為1,取大于1的8的最小倍數(shù)為8,
兩部分和為24,故sizeof(test2)=24;
(3)
struct test3 { char a; test2 bb;//見上題 int cc; }
分析:該結(jié)構(gòu)體有三個成員,其中第二個bb是類型為test2的結(jié)構(gòu)體,長度為24,且該結(jié)構(gòu)體最大長度成員類型為double型,以后成員中沒有double型,所以按bb分界為兩部分:
第一部分有a 、bb兩部分,a長度為1,bb長度為24,取8的大于25的最小倍數(shù)32;
第二部分有cc,長度為4,去8的大于4的最小倍數(shù)為8;
兩部分之和為40,故sizeof(test3)=40;
(4)
求sizeof(test5)
分析:test5明顯含有結(jié)構(gòu)體test4,按例2容易知道sizeof(test4)=8,且其成員最大長度為4;則結(jié)構(gòu)體test5的最大成員長度為8(double 型),考試.大提示e是分界點,分test5為兩部分:
第一部分由c 、d、e組成,長度為1、8、8,故和為17,取8的大于17的最小倍數(shù)為24;
第二部分由f組成,長度為1,取8的大于1的最小倍數(shù)為8,
兩部分和為32,故sizeof(test5)=24+8=32;
?八、union
union的長度取決于其中的長度最大的那個成員變量的長度。即union中成員變量是重疊擺放的,其開始地址相同。
其實union(共用體)的各個成員是以同一個地址開始存放的,每一個時刻只可以存儲一個成員,這樣就要求它在分配內(nèi)存單元時候要滿足兩點:??
? 1.一般而言,共用體類型實際占用存儲空間為其最長的成員所占的存儲空間;??
? 2.若是該最長的存儲空間對其他成員的元類型(如果是數(shù)組,取其類型的數(shù)據(jù)長度,例int?? a[5]為4)不滿足整除關(guān)系,該最大空間自動延伸;??
? 我們來看看這段代碼:???
本來mm的空間應(yīng)該是sizeof(int)*5=20;但是如果只是20個單元的話,那可以存幾個double型(8位)呢?兩個半?當(dāng)然不可以,所以mm的空間延伸為既要大于20,又要滿足其他成員所需空間的整數(shù)倍,即24???
所以union的存儲空間先看它的成員中哪個占的空間最大,拿他與其他成員的元長度比較,如果可以整除就行。
?九、指定對界
#pragma pack()命令
如何修改編譯器的默認(rèn)對齊值?
1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認(rèn)是8字節(jié)。
2.在編碼時,可以這樣動態(tài)修改:#pragma pack .注意:是pragma而不是progma.
一般地,可以通過下面的方法來改變?nèi)笔〉膶鐥l件:
使用偽指令#pragma pack (n),編譯器將按照n個字節(jié)對齊;
使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。
注意:如果#pragma pack (n)中指定的n大于結(jié)構(gòu)體中最大成員size,則其不起作用,結(jié)構(gòu)體仍然按照size最大的成員進(jìn)行對界。
為了節(jié)省空間,我們可以在編碼時通過#pragma pack()命令指定程序的對齊方式,括號中是對齊的字節(jié)數(shù),若該命令括號中的內(nèi)容為空,則為默認(rèn)對齊方式。例如,對于上面第一個結(jié)構(gòu)體,如果通過該命令手動設(shè)置對齊字節(jié)數(shù)如下:
#pragma pack(2) //設(shè)置2字節(jié)對齊
#pragma pack() // 恢復(fù)先前的pack設(shè)置,取消設(shè)置的字節(jié)對齊方式
則,num從第6(偏移量)個字節(jié)開始存放,score從第10(偏移量)個字節(jié)開始存放,故sizeof(student)=12,其數(shù)據(jù)對齊如下圖:
這樣改變默認(rèn)的字節(jié)對齊方式可以更充分地利用存儲空間,但是這會降低計算機讀寫數(shù)據(jù)的速度,是一種以時間換取空間的方式。
?十、代碼驗證
- 代碼
- 輸出
//這是默認(rèn)的結(jié)果(8字節(jié)對齊)
char: 1 long: 4 int: 4 S0: 1 S1: 8 S2: 8 S3: 16 S4: 8 S5: 16 S6: 28 union1 :16 union2 :24 student0: 16 student1: 12 student2: 12 請按任意鍵繼續(xù). . .//這是16字節(jié)對齊的結(jié)果,可以看到當(dāng)設(shè)置16字節(jié)對齊時,確實沒什么效果,里面最大的是double,也就是8字節(jié),#pragma pack (n)中指定的n大于結(jié)構(gòu)體中最大成員size,則其不起作用。
char: 1 long: 4 int: 4 double:8 S0: 1 S1: 8 S2: 8 S3: 16 S4: 8 S5: 16 S6: 28 union1 :16 union2 :24 student0: 16 student1: 12 student2: 12 請按任意鍵繼續(xù). . .//這是2字節(jié)對齊的結(jié)果,可以慢慢參考研究
char: 1 long: 4 int: 4 double:8 S0: 1 S1: 6 S2: 6 S3: 12 S4: 6 S5: 12 S6: 26 union1 :10 union2 :20 student0: 12 student1: 12 student2: 12 請按任意鍵繼續(xù). . .?
- 說明:
(1)默認(rèn)8字節(jié)對齊
(2)分析
S0:空
S1:
|char|----|----|----| |-------long--------|S2:
|-------long--------| |char|----|----|----|S3:
其中包含的S1中最長的為long,S3中也為long,以最長的為分界,那么為:1+8+4 = 13,那么這個結(jié)構(gòu)體的長度就是8的倍數(shù)16。
內(nèi)存是怎么樣的現(xiàn)在還沒有弄清楚。。。
S4:
靜態(tài)變量存放在全局?jǐn)?shù)據(jù)區(qū)內(nèi),而sizeof計算棧中分配的空間的大小,故不計算在內(nèi),S4的大小為4+4=8。
S5,S6,Student見上面例子。
union1:
最長double=8,但char c[9]用9個不夠,再加一倍到16.
union2:
類型最長的是long=8,變量最長的是int b[5] = 4*5=20,20以上8的倍數(shù)為24。
?十一、還沒有解決的問題
雖然知道結(jié)構(gòu)體中含有結(jié)構(gòu)體的長度怎么計算,但不知道它的內(nèi)存是什么樣子的,在VS中用
cout << "&objS3.a: "<< hex << &objS3.a << endl;為什么顯示出來是亂碼??
十二、字節(jié)對齊可能帶來的隱患
(說明:從一個pdf復(fù)制,參考一下)
代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強制類型轉(zhuǎn)換的時候。例如:
最后兩句代碼,從奇數(shù)邊界去訪問unsignedshort型變量,顯然不符合對齊的規(guī)定。
在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須字節(jié)對齊。
十三、參考引用
在上述內(nèi)容中,引用參考了不少文章,現(xiàn)將鏈接給出,同時感謝Scorpions帶來的音樂快感。這里僅供本人學(xué)習(xí),謝謝作者。
http://blog.csdn.net/houghstc/archive/2009/06/30/4307523.aspx
http://blog.csdn.net/vincent_1011/archive/2009/08/25/4479965.aspx
http://www.baidu.com/index.php
http://apps.hi.baidu.com/share/detail/6503863
http://hmmanhui.blog.sohu.com/108007380.html
http://www.cppreference.com/wiki/keywords/sizeof
http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
總結(jié)
以上是生活随笔為你收集整理的C++之字节对齐与结构体大小的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android中判断sim卡状态和读取联
- 下一篇: C的无符号数据类型int,short,b