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