C/C++以內存對齊

數據對齊,是指數據所在的內存地址必須是該數據長度的整數倍。DWORD數據的內存起始地址能被4除盡,WORD數據的內存起始地址能被2除盡。X86 CPU能直接訪問對齊的數據,當它試圖訪問一個未對齊的數據時,會在內部進行一系列的調整。這些調整對於程序員來講是透明的,可是會下降運行速度,因此編譯器在編譯程序時會盡可能保證數據對齊。ios

不一樣的編譯器內存對齊的方式不一樣。程序員

一個小例子:在32位的機器上,數據是以4字節爲對齊單位,這兩個類的輸出結果爲何不一樣?(VS2008)數組

 

[cpp]  view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class B  
  5. {  
  6. private:  
  7. bool m_bTemp;  
  8. int m_nTemp;  
  9. bool m_bTemp2;  
  10. };  
  11.   
  12. class C  
  13.   
  14. {  
  15. private:  
  16. int m_nTemp;  
  17. bool m_bTemp;  
  18. bool m_bTemp2;  
  19. };  
  20.   
  21. int _tmain(int argc, _TCHAR* argv[])  
  22.   
  23. {  
  24. cout<<sizeof(B)<<endl;  
  25. cout<<sizeof(C)<<endl;  
  26. system("pause");  
  27. return 0;  
  28.   
  29. }  

答案是:3*4=12,2*4=8佈局

分析:在訪問內存時,若是地址按4字節對齊,則訪問的效率會高不少。性能

考慮到性能方面,編譯器會對結構進行對齊處理,考慮下面的結構:spa

struct aStruct.net

{blog

char cValue;內存

int iValue;get

};

直觀地講,這個結構的尺寸是sizeof(char)+sizeof(int)=6,可是在實際編譯下, 這個結構尺寸默認是8,由於第二個域iValue會被對齊到第4個字節。

注意:字節對齊是編譯時決定的,一旦決定則不會再改變,所以即便有對齊的因素存在,也不會出現一個結構在運行時尺寸發生變化的狀況。

在本題中:第一種類的數據對齊是下面的狀況:

bool ---- ---- ----

------- int ---------

bool ----- ---- ----

第二種類的數據對齊是下面的狀況:

------- int ----------

bool bool ---------

因此類的大小分別3*4和2*4

通常在VC++中加上#pragma pack(n)設置內存對齊。

咱們能夠利用#pragma pack()來改變編譯器的默認對齊方式。

#pragma pack(n)   //編譯器將按照n字節對齊

#pragma pack()     //編譯器將取消自定義字節對齊方式

在#pragma pack(n)和#pragma pack()之間的代碼按n字節對齊。

可是成員對齊有一個重要的條件,即每一個成員按照本身的對齊方式對齊;也就是說雖然指定了按n字節對齊,但並非全部的成員都以n字節對齊。

對齊的規則是:每一個成員按其類型的對齊參數(一般是這個類型的大小)和指定對齊參數(這裏是n字節)中較小的一個對齊,即min(n,sizeof(item)),而且結構的長度必須爲所用過的全部對齊參數的整數倍,不夠就補空字節。

[cpp]  view plain copy
  1. #pragma pack(8)  
  2. struct TestStruct4  
  3. {  
  4.  char a;  
  5.  long b;  
  6. };  
  7. struct TestStruct5  
  8. {  
  9.  char c;  
  10.  TestStruct4 d;  
  11.  long long e;  
  12. };  
  13. int _tmain(int argc, _TCHAR* argv[])  
  14. {  
  15.  cout<<sizeof(TestStruct4)<<endl;  
  16.  cout<<sizeof(TestStruct5)<<endl;  
  17.  system("pause");  
  18.  return 0;  
  19. }  

 

運行結果爲:8,24

分析:

TestStruct4 中,成員a是1字節,默認按照1字節對齊,指定對齊參數是8,這兩個值中取1,a按1字節對齊;

成員b是4字節,默認是按4字節對齊,這時就按4字節對齊,因此sizeof(TestStruct4)應該是8.

TestStruct5 中,c和TestStruct4中的a同樣,按1字節對齊;而d是個結構,它是8字節,對於結構來講,它的默認對齊方式就是其全部成員使用的對齊參數中最大的一個,TestStruct4就是4,因此成員d就按照4字節對齊。成員e是8字節,它是默認的8字節對齊,和指定的同樣,因此它對齊到8本身的邊界上,這時,已經使用了12字節了,因此又添加了4字節的空間,從第16字節開始放置成員e。這時長度爲24,已經能夠被8整除(成員e按8字節對齊)。這樣一共使用了24字節。

內存佈局圖以下:

TestStruct4 的內存佈局:

  a      b

1***   1111 

TestStruct5 的內存佈局:

      c     d.a      d.b                e

1***   1***     1111    ****    11111111

注意3點:

(1)每一個成員按照本身的方式對齊,並能最小化長度

(2)複雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是複雜類型時,能夠最小化長度。

(3)對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時能夠保證每一項都邊界對齊。

補充:對於數組,好比說char a[3],它的對齊方式和分別寫3個char是同樣的,也就是說它仍是按1字節對齊;若是寫成typedef char Arrary3[3],Arrary3這種類型的對齊方式仍是按1字節對齊,而不是按它的長度。

可是不論類型是什麼,對齊的邊界必定是一、二、四、八、1六、3二、64......中的一個。

下面來講下大端模式和小端模式

大端模式:認爲第一個字節是最高位字節,也就說按照從低地址到高地址的順序存放數據的高位字節到低位字節。

小端模式:認爲第一個字節是最低位字節,也就是說按照從低地址到高地址的順序存放數據的低位字節到高位字節。

假設從內存地址0x0000開始有如下數據:

內存地址:0x0000    0x0001    0x0002     0x0003

對應數據:0x12       0x34      0x56       0x78

若是咱們去讀取一個地址爲0x0000的4字節變量

若字節序位爲小端模式,讀出爲:0x78563412

若字節序位爲大端模式,讀出爲:0x12345678

通常來講:X86系列的CPU都是小端字節序,powerPC一般是大端字節序。

[cpp]  view plain copy
  1. int _tmain(int argc, _TCHAR* argv[])  
  2.   
  3. {  
  4.      char *sz = "0123456789";  
  5.      int *p = (int *)sz;  
  6.      cout<<hex<<*++p<<endl;  
  7. }  

運行結果爲:37363534

分析:這裏是小端字節序

地址從0x0000開始,那麼sz在內存中的存儲爲:

內存地址:     0x00  0x01  0x02   0x03   0x04  0x05  0x06   0x07   0x08  0x09 

對應的值:      0      1     2      3      4     5     6      7     8      9

對應的值:      48     49    50     51     52    53    54     55    56     57

對應的16進制:0x30   0x31  0x32   0x33   0x34  0x35  0x36   0x37  0x38   0x39

                sz                         ++p

因此讀取爲:0x37363534

相關文章
相關標籤/搜索