天天看點

C 中的位元組對齊和位域

一、首先給段代碼,如果能确切的知道這段代碼的輸出,那說明對位元組對齊和位域了解的已經差不多了!

[java] view plain copy print ?

  1. main(){  
  2.     struct student{int a;char b;short c;};  
  3.     struct teacher{char b;int a;short c;};  
  4.     struct bs{int a:32;int b:8;int c:8;int d:8;int e:8;};  
  5.     struct bs2{int a;int b;int c;int d;int e;};  
  6.     int n=sizeof(struct student);  
  7.     int m=sizeof(struct teacher);  
  8.     int p=sizeof(struct bs);  
  9.     int q=sizeof(struct bs2);  
  10.     printf("%d/n%d/n%d/n%d/n",n,m,p,q);  
  11.  }  

main(){

struct student{int a;char b;short c;};

struct teacher{char b;int a;short c;};

struct bs{int a:32;int b:8;int c:8;int d:8;int e:8;};

struct bs2{int a;int b;int c;int d;int e;};

int n=sizeof(struct student);

int m=sizeof(struct teacher);

int p=sizeof(struct bs);

int q=sizeof(struct bs2);

printf("%d/n%d/n%d/n%d/n",n,m,p,q);

}

給出結果:我的環境:sizeof(int)==4。

C 中的位元組對齊和位域

二、位元組對齊(整理N多網友的介紹)。

        現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的通路可以從任何位址開始,但實際情況是在通路特定變量的時候經常在特定的記憶體位址通路,這就需要各類型資料按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。各個硬體平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的資料隻能從某些特定位址開始存取。其他平台可能沒有這種情況,但是最常見的是如果不按照适合其平台的要求對資料存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶位址開始,如果一個int型(假設為32位)如果存放在偶位址開始的地方,那麼一個讀周期就可以讀出,而如果存放在奇位址開始的地方,就可能會需要2個讀周期,并對兩次讀出的結果的高低位元組進行拼湊才能得到該int資料。顯然在讀取效率上下降很多。

設結構體如下定義:

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)

struct C

{

    char b;

    int a;

    short c;

};

#pragma pack ()

sizeof(struct C)值是8。

修改對齊值為1:

#pragma pack (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)

struct C

{

    char b;

    int a;

    short c;

};

#pragma pack ()

第一個變量b的自身對齊值為1,指定對齊值為2,是以,其有效對齊值為1,假設C從0x0000開始,那麼b存放在0x0000,符合0x0000%1= 0;第二個變量,自身對齊值為4,指定對齊值為2,是以有效對齊值為2,是以順序存放在0x0002、0x0003、0x0004、0x0005四個連續位元組中,符合0x0002%2=0。第三個變量c的自身對齊值為2,是以有效對齊值為2,順序存放

在0x0006、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成員對我們的程式沒有什麼意義,它隻是起到填補空間以達到位元組對齊的目的,當然即使不加這個成員通常編譯器也會給我們自動填補對齊,我們自己加上它隻是起到顯式的提醒作用.

位元組對齊可能帶來的隐患:

代碼中關于對齊的隐患,很多是隐式的。比如在強制類型轉換的時候。例如:

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,因為它們要求必須位元組對齊.

如何查找與位元組對齊方面的問題:

如果出現對齊或者指派問題首先檢視

1. 編譯器的big little端設定

2. 看這種體系本身是否支援非對齊通路

3. 如果支援,看設定了對齊與否,如果沒有則看通路時需要加某些特殊的修飾來标志其特殊通路操作。

三 、位域讨論(整理N多網友讨論)

1.位域又叫位段(位字段),是一種特殊的結構成員或聯合成員(即隻能用在結構或聯合中),用于指定該成員在記憶體存儲時所占用的位數,進而可以在機内更緊湊地表示資料。

2.位域的使用主要出現在如下兩種情況:

(1)當機器可用記憶體空間較少而使用位域可以大量節省記憶體時。如,當把結構作為大數組的元素時。

(2)當需要把一結構或聯合映射成某預定的組織結構時。例如,當需要通路位元組内的特定位時。

3.當要把某個成員說明成位域時,其類型隻能是int,unsigned int與signed int三者之一(說明:int類型通常代表特定機器中整數的自然長度。short類型通常為16位,long類型通常為32位,int類型可以為16位或32位.各編譯器可以根據硬體特性自主選擇合适的類型長度.見The C Programming Language中文 P32)。

4.帶位域的結構在記憶體中各個位域的存儲方式取決于具體的編譯程式;它們既可以從左到右,也可以從右到左存儲。

5.在一包含位域說明的結構或聯合區分符中也可以同時說明普通成員,例如:

    struct st1

    {

       unsigned a:7;

       unsigned b:4;

       unsigned c:5;

       int      i;     //i是普通成員,這會被存放在下一個字,即字對齊

     };

6.int值不能跨字存放,同樣位域也最好不要跨字存放(意思說是說:各位域的配置設定位數加起來要在16位或32位以内,如果編譯器配置設定int為16位,則加起來要在16位以内,如果加起來大于16位,則最好空出剩餘的位域,從下一個字開始配置設定位域).

7.特殊寬度0或者說長度為0的無名位域有着特殊的用途.它用于訓示将其前後的兩個位域或成員分開放在兩個字中, 即将位于該無名位域後的下一個位域從下一個字開始存放.

關于位域還需要提醒讀者注意如下幾點:

其一,位域的長度不能大于int對象所占用的字位數.例如,若int對象占用16位,則如下位域說明是錯誤的:     unsigned int x:17;

其二,由于位域的實作會因編譯程式的不同而不同,在此使用位域會影響程式的可移植性,在不是非要使用位域不可時最好不要使用位域.

其三,盡管使用位域可以節省記憶體空間,但卻增加了處理時間,在為當通路各個位域成員時需要把位域從它所在的字中分解出來或反過來把一值壓縮存到位域所在的字位中.

其四,位域的位置不能通路,因些不能對位域使用位址運算符号&(而對非位域成員則可以使用該運算符).進而,即不能使用指向位域的旨針也不能使用位域的數組(因為數組實際上就是一種特殊的指針).另外,位域也不能作為函數傳回的結果.

最後還要強調一遍:位域又叫位段(位字段),是一種特殊的結構成員或聯合成員(即隻能用在結構或聯合中).

http://blog.csdn.net/huangyunzeng2008/article/details/5899453

繼續閱讀