什麼是大端和小端

什麼是大端和小端

        Big-Endian和Little-Endian的定義如下:
1) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
2) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
舉一個例子,比如數字0x12 34 56 78在內存中的表示形式爲:

1)大端模式:

低地址 -----------------> 高地址
0x12  |  0x34  |  0x56  |  0x78

2)小端模式:

低地址 ------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

可見,大端模式和字符串的存儲模式類似。

3)下面是兩個具體例子:

16bit寬的數0x1234在Little-endian模式(以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)爲:
 
內存地址 小端模式存放內容 大端模式存放內容
0x4000 0x34 0x12
0x4001 0x12 0x34

32bit寬的數0x12345678在Little-endian模式以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)爲:

內存地址 小端模式存放內容 大端模式存放內容
0x4000 0x78 0x12
0x4001 0x56 0x34
0x4002 0x34 0x56
0x4003 0x12 0x78
 

 4)大端小端沒有誰優誰劣,各自優勢便是對方劣勢:

小端模式 :強制轉換數據不需要調整字節內容,1、2、4字節的存儲方式一樣。
大端模式 :符號位的判定固定爲第一個字節,容易判斷正負。

 

數組在大端小端情況下的存儲:

  以unsigned int value = 0x12345678爲例,分別看看在兩種字節序下其存儲情況,我們可以用unsigned char buf[4]來表示value:
  Big-Endian: 低地址存放高位,如下:
高地址
        ---------------
        buf[3] (0x78) -- 低位
        buf[2] (0x56)
        buf[1] (0x34)
        buf[0] (0x12) -- 高位
        ---------------
        低地址
Little-Endian: 低地址存放低位,如下:
高地址
        ---------------
        buf[3] (0x12) -- 高位
        buf[2] (0x34)
        buf[1] (0x56)
        buf[0] (0x78) -- 低位
        --------------

低地址

 

爲什麼會有大小端模式之分呢?

      這是因爲在計算機系統中,我們是以字節爲單位的,每個地址單元都對應着一個字節,一個字節爲8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於8位的處理器,例如16位或者32位的處理器,由於寄存器寬度大於一個字節,那麼必然存在着一個如果將多個字節安排的問題。因此就導致了大端存儲模式和小端存儲模式。例如一個16bit的short型x,在內存中的地址爲0x0010,x的值爲0x1122,那麼0x11爲高字節,0x22爲低字節。對於大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反。我們常用的X86結構是小端模式,而KEIL C51則爲大端模式。很多的ARM,DSP都爲小端模式。有些ARM處理器還可以由硬件來選擇是大端模式還是小端模式。

 

如何判斷機器的字節序

可以編寫一個小的測試程序來判斷機器的字節序:

 

  1. BOOL IsBigEndian()    
  2. {    
  3.     int a = 0x1234;    
  4.     char b =  *(char *)&a;  //通過將int強制類型轉換成char單字節,通過判斷起始存儲位置。即等於 取b等於a的低地址部分    
  5.     if( b == 0x12)    
  6.     {    
  7.         return TRUE;    
  8.     }    
  9.     return FALSE;    
  10. }  

聯合體union的存放順序是所有成員都從低地址開始存放,利用該特性可以輕鬆地獲得了CPU對內存採用Little-endian還是Big-endian模式讀寫

 

  1. BOOL IsBigEndian()    
  2. {    
  3.     union NUM    
  4.     {    
  5.         int a;    
  6.         char b;    
  7.     }num;    
  8.     num.a = 0x1234;    
  9.     if( num.b == 0x12 )    
  10.     {    
  11.         return TRUE;    
  12.     }    
  13.     return FALSE;    
  14. }  

 

內存對齊問題

 

怎麼判斷內存對齊規則,sizeof的結果怎麼來的,請牢記以下3條原則:(在沒有#pragma pack宏的情況下,務必看完最後一行)

1:數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數組,結構體等)的整數倍開始(比如int在32位機爲4字節,則要從4的整數倍地址開始存儲。

2:結構體作爲成員:如果一個結構裏有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲.(struct a裏存有struct b,b裏有char,int ,double等元素,那b應該從8的整數倍開始存儲.)

3:收尾工作:結構體的總大小,也就是sizeof的結果,.必須是其內部最大成員的整數倍.不足的要補齊.

  1. typedef struct bb  
  2. {  
  3.  int id;             //[0]....[3]  
  4.  double weight;      //[8].....[15]      原則1  
  5.  float height;      //[16]..[19],總長要爲8的整數倍,補齊[20]...[23]     原則3  
  6. }BB;  
  7.   
  8. typedef struct aa  
  9. {  
  10.  char name[2];     //[0],[1]  
  11.  int  id;         //[4]...[7]          原則1   
  12.  double score;     //[8]....[15]      
  13.  short grade;    //[16],[17]          
  14.  BB b;             //[24]......[47]       原則2  
  15. }AA;  
  16.   
  17. int main()  
  18. {  
  19.   AA a;  
  20.   cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;  
  21.   return 0;  
  22. }   

結果是

48 24
ok,上面的全看明白了,內存對齊基本過關.

再講講#pragma pack().

在代碼前加一句#pragma pack(1),你會很高興的發現,上面的代碼輸出爲

32 16
bb是4+8+4=16,aa是2+4+8+2+16=32;

這不是理想中的沒有內存對齊的世界嗎.沒錯,#pragmapack(1),告訴編譯器,所有的對齊都按照1的整數倍對齊,換句話說就是沒有對齊規則.

明白了不?

那#pragma pack(2)的結果又是多少呢?對不起,5分鐘到了,自己去測試吧. 

ps:Vc,Vs等編譯器默認是#pragma pack(8),所以測試我們的規則會正常;注意gcc默認是#pragma pack(4),並且gcc只支持1,2,4對齊。套用三原則裏計算的對齊值是不能大於#pragma pack指定的n值。

 

內存對齊二

VC對結構的存儲的特殊處理確實提高CPU存儲變量的速度,但是有時候也帶來了一些麻煩,我們也屏蔽掉變量默認的對齊方式,自己可以設定變量的對齊方式。VC 中提供了#pragma pack(n)來設定變量以n字節對齊方式。n字節對齊就是說變量存放的起始地址的偏移量有兩種情況:

第一、如果n大於等於該變量所佔用的字節數,那麼偏移量必須滿足默認的對齊方式;

第二、如果n小於該變量的類型所佔用的字節數,那麼偏移量爲n的倍數,不用滿足默認的對齊方式。

結構的總大小也有個約束條件,分下面兩種情況:如果n大於所有成員變量類型所佔用的字節數,那麼結構的總大小必須爲佔用空間最大的變量佔用的空間數的倍數;否則必須爲n的倍數。下面舉例說明其用法:

  1. #pragma pack(push) //保存對齊狀態   
  2. #pragma pack(4)//設定爲4字節對齊   
  3. struct test   
  4. {   
  5. char m1;   
  6. double m4;   
  7. int m3;   
  8. };   
  9. #pragma pack(pop)//恢復對齊狀態   

以上結構的大小爲16,下面分析其存儲情況,首先爲m1分配空間,其偏移量爲0,滿足我們自己設定的對齊方式(4字節對齊),m1佔用1個字節。接着開始爲 m4分配空間,這時其偏移量爲1,需要補足3個字節,這樣使偏移量滿足爲n=4的倍數(因爲sizeof(double)大於n),m4佔用8個字節。接着爲m3分配空間,這時其偏移量爲12,滿足爲4的倍數,m3佔用4個字節。這時已經爲所有成員變量分配了空間,共分配了4+8+4=16個字節,滿足爲n的倍數。如果把上面的#pragma pack(4)改爲#pragma pack(16),那麼我們可以得到結構的大小爲24。

再看下面這個例子:

  1. #pragma pack(8)  
  2. struct S1{  
  3.  char a;  
  4.  long b;  
  5. };   
  6.   
  7. struct S2 {  
  8.  char c;  
  9.  struct S1 d;  
  10.  long long e;  
  11. };  
  12. #pragma pack()  

成員對齊有一個重要的條件,即每個成員分別對齊.即每個成員按自己的方式對齊.

也就是說上面雖然指定了按8字節對齊,但並不是所有的成員都是以8字節對齊.其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這裏是8字節)中較小的一個對齊.並且結構的長度必須爲所用過的所有對齊參數的整數倍,不夠就補空字節.

S1中,成員a是1字節默認按1字節對齊,指定對齊參數爲8,這兩個值中取1,a按1字節對齊;成員b是4個字節,默認是按4字節對齊,這時就按4字節對齊,所以sizeof(S1)應該爲8;

S2 中,c和S1中的a一樣,按1字節對齊,而d 是個結構,它是8個字節,它按什麼對齊呢?對於結構來說,它的默認對齊方式就是它的所有成員使用的對齊參數中最大的一個,S1的就是4.所以,成員d就是 按4字節對齊.成員e是8個字節,它是默認按8字節對齊,和指定的一樣,所以它對到8字節的邊界上,這時,已經使用了12個字節了,所以又添加了4個字節的空,從第16個字節開始放置成員e.這時,長度爲24,已經可以被8(成員e按8字節對齊)整除.這樣,sizeof(S2)爲24個字節.

這裏有三點很重要:

1.每個成員分別按自己的方式對齊,並能最小化長度。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章