<摘錄>位元組對齊與結構體大小

木子你妹發表於2013-06-17

說明:

結 構體的sizeof值,並不是簡單的將其中各元素所佔位元組相加,而是要考慮到儲存空間的位元組對齊問題。這些問題在平時程式設計的時候也確實不怎麼用到,但在一 些筆試面試題目中出是常常出現,對sizeof我們將在另一篇文章中總結,這篇文章我們只總結結構體的sizeof,報著不到黃河心不死的決心,終於完成 了總結,也算是小有收穫,拿出來於大家分享,如果有什麼錯誤或者沒有理解透的地方還望能得到提點,也不至於誤導他人。

別忘了這裡 http://pppboy.blog.163.com/blog/static/30203796201082494026399/

一、解釋

現代計算機中記憶體空間都是按照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).將各個部分長度相加之和即為結構體長度

 

五、空結構體

 

struct S5 { };  
sizeof( S5 ); // 結果為1

“空結構體”(不含資料成員)的大小不為0,而是1。試想一個“不佔空間”的變數如何被取地址、兩個不同的“空結構體”變數又如何得以區分呢於是,“空結構體”變數也得被儲存,這樣編譯器也就只能為其分配一個位元組的空間用於佔位了。

六、有static的結構體

struct S4{ 
    char a; 
    long b; 
    static long c; //靜態 
}; 

靜態變數存放在全域性資料區內,而sizeof計算棧中分配的空間的大小,故不計算在內,S4的大小為4+4=8。

 

七、舉例說明

1.舉例1


很顯然預設對齊方式會浪費很多空間,例如如下結構:

struct student 
{ 
    char name[5]; 
    int num; 
    short score; 
}


本 來只用了11bytes(5+4+2)的空間,但是由於int型預設4位元組對齊,存放在地址能被4整除的起始位置,即:如果name[5]從0開始存放, 它佔5bytes,而num則從第8(偏移量)個位元組開始存放。所以sizeof(student)=16。於是中間空出幾個位元組閒置著。但這樣便於計算 機快速讀寫資料,是一種以空間換取時間的方式。其資料對齊如下圖:

|char|char|char|char| 
|char|----|----|----| 
|--------int--------| 
|--short--|----|----| 

如果我們將結構體中變數的順序改變為:

struct student 
{ 
    int num; 
    char name[5]; 
    short score; 
}


則,num從0開始存放,而name從第4(偏移量)個位元組開始存放,連續5個位元組,score從第10(偏移量)開始存放,故sizeof(student)=12。其資料對齊如下圖:

|--------int--------| 
|char|char|char|char| 
|char|----|--short--| 

如果我們將結構體中變數的順序再次改為為:

struct student 
{ 
    int num; 
    short score; 
    char name[5]; 
}


則,sizeof(student)=12。其資料對齊如下圖:

|--------int--------| 
|--short--|char|char| 
|char|char|char|----| 
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)

struct test4 
{ 
 char a; 
 int b; 
}; 
struct test5 
{ char c; 
 test4 d; 
 double e; 
 bool f; 
};


求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)不滿足整除關係,該最大空間自動延伸;  
  我們來看看這段程式碼:   

  union   mm{    
  char   a;//元長度1    
  int   b[5];//元長度4    
  double   c;//元長度8    
  int   d[3];    
  };   

本來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位元組對齊

struct strdent  
{  
    char name[5]; //本身1位元組對齊,比2位元組對齊小,按1位元組對齊  
    int num;          //本身4位元組對齊,比2位元組對齊大,按2位元組對齊  
    short score;    //本身也2位元組對齊,仍然按2位元組對齊  
} 


#pragma pack() // 恢復先前的pack設定,取消設定的位元組對齊方式

則,num從第6(偏移量)個位元組開始存放,score從第10(偏移量)個位元組開始存放,故sizeof(student)=12,其資料對齊如下圖:

|char|char|  
|char|char|  
|char|----| 
|----int--| 
|----int--| 
|--short--| 

這樣改變預設的位元組對齊方式可以更充分地利用儲存空間,但是這會降低計算機讀寫資料的速度,是一種以時間換取空間的方式。

 

十、程式碼驗證

  • 程式碼
//------------------------------------ 
// 環境:VS2005 
// 時間:2010.9.24 
// 用途:結構體大小測試 
// 作者:pppboy.blog.163.com
//----------------------------------- 
#include "stdafx.h" 
#include <iostream> 
using namespace std; 
//空 
struct S0{  }; 
struct S1{ 
    char a; 
    long b; 
}; 
struct S2{ 
    long b; 
    char a; 
}; 
struct S3 { 
    char c; 
    struct S1 d;//結構體 
    long e; 
}; 
struct S4{ 
    char a; 
    long b; 
    static long c; //靜態 
}; 
struct S5{ 
    char a; 
    long b; 
    char name[5]; //陣列 
}; 
//含有一個陣列 
struct S6{ 
    char a; 
    long b; 
    int name[5]; //陣列 
}; 
struct student0 
{ 
    char name[5]; 
    int num; 
    short score; 
}; 
struct student1 
{ 
    int num; 
    char name[5]; 
    short score; 
}; 
struct student2 
{ 
    int num; 
    short score; 
    char name[5]; 
}; 
union union1 
{ 
    long a; 
    double b; 
    char name[9]; 
}; 
union   union2{    
    char a; 
    int b[5];  
    double  c; 
    int d[3];  
};    
int main(int argc, char* argv[]) 
{ 
    cout << "char: " << sizeof(char) << endl; //1 
    cout << "long: " << sizeof(long) << endl; //4 
    cout << "int:  " << sizeof(int) << endl; //4 
    cout << "S0: " << sizeof(S0) << endl; //1 
    cout << "S1: " << sizeof(S1) << endl; //8 
    cout << "S2: " << sizeof(S2) << endl; //8 
    cout << "S3: " << sizeof(S3) << endl; //24 
    cout << "S4: " << sizeof(S4) << endl; //8 
    cout << "S5: " << sizeof(S5) << endl; //16 
    cout << "S6: " << sizeof(S6) << endl; //28 
    cout << "union1 :" << sizeof(union1) << endl; 
    cout << "union2 :" << sizeof(union2) << endl; 
    cout << "student0: " << sizeof(student0) << endl; 
    cout << "student1: " << sizeof(student1) << endl; 
    cout << "student2: " << sizeof(student2) << endl; 
    system("pause"); 
    return 0; 
} 
  • 輸出

//這是預設的結果(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複製,參考一下)

程式碼中關於對齊的隱患,很多是隱式的。比如在強制型別轉換的時候。例如:

unsigned int i = 0x12345678; 
unsigned char *p=NULL; 
unsigned short *p1=NULL; 
p=&i; 
*p=0x00; 
p1=(unsigned short *)(p+1); 
*p1=0x0000;


最後兩句程式碼,從奇數邊界去訪問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

本文章出處:
http://pppboy.blog.163.com/blog/static/30203796201082494026399/

相關文章