<摘錄>字節對齊與結構體大小

說明:

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

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

1、解釋

現代計算機中內存空間都是按照byte劃分的,從理論上講彷佛對任何類型的變量的訪問能夠從任何地址開始,但實際狀況是在訪問特定類型變量的時候常常在特 定的內存地址訪問,這就須要各類類型數據按照必定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。ios

各 個硬件平臺對存儲空間的處理上有很大的不一樣。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。好比有些架構的CPU在訪問 一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其餘平臺可能沒有這種狀況,可是最多見的是若是不按照適合其平臺要求對 數據存放進行對齊,會在存取效率上帶來損失。好比有些平臺每次讀都是從偶地址開始,若是一個int型(假設爲32位系統)若是存放在偶地址開始的地方,那 麼一個讀週期就能夠讀出這32bit,而若是存放在奇地址開始的地方,就須要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能獲得該32bit數 據。c++

 

2、準則

其實字節對齊的細節和具體編譯器實現相關,但通常而言,知足三個準則:面試

1. 結構體變量的首地址可以被其最寬基本類型成員的大小所整除;編程

2. 結構體每一個成員相對於結構體首地址的偏移量都是成員大小的整數倍,若有須要編譯器會在成員之間加上填充字節;數組

3. 結構體的總大小爲結構體最寬基本類型成員大小的整數倍,若有須要編譯器會在最末一個成員以後加上填充字節。架構

 

3、基本概念


字 節對齊:計算機存儲系統中以Byte爲單位存儲數據,不一樣數據類型所佔的空間不一樣,如:整型(int)數據佔4個字節,字符型(char)數據佔一個字 節,短整型(short)數據佔兩個字節,等等。計算機爲了快速的讀寫數據,默認狀況下將數據存放在某個地址的起始位置,如:整型數據(int)默認存儲 在地址能被4整除的起始位置,字符型數據(char)能夠存放在任何地址位置(被1整除),短整型(short)數據存儲在地址能被2整除的起始位置。這 就是默認字節對齊方式。 app

 

4、結構體長度求法

1.成員都相同時(或含數組且數組數據類型同結構體其餘成員數據類型):
結構體長度=成員數據類型長度×成員個數(各成員長度之和);
結構體中數組長度=數組數據類型長度×數組元素個數;學習

2.成員不一樣且不含其它結構體時;
(1).分析各個成員長度;
(2).找出最大長度的成員長度M(結構體的長度必定是該成員的整數倍);
(3).並按最大成員長度出現的位置將結構體分爲若干部分;
(4).各個部分長度一次相加,求出大於該和的最小M的整數倍即爲該部分長度
(5).將各個部分長度相加之和即爲結構體長度

3.含有其餘結構體時:
(1).分析各個成員長度;
(2).對是結構體的成員,其長度按b來分析,且不會隨着位置的變化而變化;
(3).分析各個成員的長度(成員爲結構體的分析其成員長度),求出最大值;
(4).若長度最大成員在爲結構體的成員中,則按結構體成員爲分界點分界;
其餘成員中有最大長度的成員,則該成員爲分界點;
求出各段長度,求出大於該和的最小M的整數倍即爲該部分長度
(5).將各個部分長度相加之和即爲結構體長度

 

5、空結構體

 

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

「空結構體」(不含數據成員)的大小不爲0,而是1。試想一個「不佔空間」的變量如何被取地址、兩個不一樣的「空結構體」變量又如何得以區分呢因而,「空結構體」變量也得被存儲,這樣編譯器也就只能爲其分配一個字節的空間用於佔位了。

6、有static的結構體

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

靜態變量存放在全局數據區內,而sizeof計算棧中分配的空間的大小,故不計算在內,S4的大小爲4+4=8。

 

7、舉例說明

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組成,長度爲一、八、8,故和爲17,取8的大於17的最小倍數爲24;
第二部分由f組成,長度爲1,取8的大於1的最小倍數爲8,
兩部分和爲32,故sizeof(test5)=24+8=32;

 

8、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的存儲空間先看它的成員中哪一個佔的空間最大,拿他與其餘成員的元長度比較,若是能夠整除就行

 

9、指定對界

#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--| 

這樣改變默認的字節對齊方式能夠更充分地利用存儲空間,可是這會下降計算機讀寫數據的速度,是一種以時間換取空間的方式。

 

10、代碼驗證

  • 代碼
//------------------------------------ 
// 環境: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。

 

11、尚未解決的問題

雖然知道結構體中含有結構體的長度怎麼計算,但不知道它的內存是什麼樣子的,在VS中用

cout << "&objS3.a: "<< hex  << &objS3.a << endl; 

爲何顯示出來是亂碼??

12、字節對齊可能帶來的隱患

(說明:從一個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,由於它們要求必須字節對齊。

十3、參考引用

在上述內容中,引用參考了很多文章,現將連接給出,同時感謝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/
相關文章
相關標籤/搜索