1. 程式人生 > >計算機大端模式和小端模式 記憶體對齊問題(sizeof)

計算機大端模式和小端模式 記憶體對齊問題(sizeof)

一、大端模式和小端模式的起源

        關於大端小端名詞的由來,有一個有趣的故事,來自於Jonathan Swift的《格利佛遊記》:Lilliput和Blefuscu這兩個強國在過去的36個月中一直在苦戰。戰爭的原因:大家都知道,吃雞蛋的時候,原始的方法是打破雞蛋較大的一端,可以那時的皇帝的祖父由於小時侯吃雞蛋,按這種方法把手指弄破了,因此他的父親,就下令,命令所有的子民吃雞蛋的時候,必須先打破雞蛋較小的一端,違令者重罰。然後老百姓對此法令極為反感,期間發生了多次叛亂,其中一個皇帝因此送命,另一個丟了王位,產生叛亂的原因就是另一個國家Blefuscu的國王大臣煽動起來的,叛亂平息後,就逃到這個帝國避難。據估計,先後幾次有11000餘人情願死也不肯去打破雞蛋較小的端吃雞蛋。這個其實諷刺當時英國和法國之間持續的衝突。Danny Cohen一位網路協議的開創者,第一次使用這兩個術語指代位元組順序,後來就被大家廣泛接受。
 

二、什麼是大端和小端

        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處理器還可以由硬體來選擇是大端模式還是小端模式。

五、如何判斷機器的位元組序

可以編寫一個小的測試程式來判斷機器的位元組序:

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  
BOOL IsBigEndian()  
{  
    int a = 0x1234;  
    char b =  *(char *)&a;  //通過將int強制型別轉換成char單位元組,通過判斷起始儲存位置。即等於 取b等於a的低地址部分  
    if( b == 0x12)  
    {  
        return TRUE;  
    }  
    return FALSE;  
}

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

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  
    BOOL IsBigEndian()  
    {  
        union NUM  
        {  
            int a;  
            char b;  
        }num;  
        num.a = 0x1234;  
        if( num.b == 0x12 )  
        {  
            return TRUE;  
        }  
        return FALSE;  
    }

記憶體對齊問題

怎麼判斷記憶體對齊規則,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的結果,.必須是其內部最大成員的整數倍.不足的要補齊.

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. typedefstruct 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. typedefstruct aa  
  8. {  
  9.  char name[2];     //[0],[1]
  10.  int  id;         //[4]...[7]          原則1 
  11.  double score;     //[8]....[15]    
  12.  short grade;    //[16],[17]        
  13.  BB b;             //[24]......[47]       原則2
  14. }AA;  
  15. int main()  
  16. {  
  17.   AA a;  
  18.   cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;  
  19.   return 0;  
  20. }   
typedef struct bb
{
 int id;             //[0]....[3]
 double weight;      //[8].....[15]      原則1
 float height;      //[16]..[19],總長要為8的整數倍,補齊[20]...[23]     原則3
}BB;

typedef struct aa
{
 char name[2];     //[0],[1]
 int  id;         //[4]...[7]          原則1 
 double score;     //[8]....[15]    
 short grade;    //[16],[17]        
 BB b;             //[24]......[47]       原則2
}AA;

int main()
{
  AA a;
  cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
  return 0;
} 

結果是

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的倍數。下面舉例說明其用法:

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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)//恢復對齊狀態 
#pragma pack(push) //儲存對齊狀態 
#pragma pack(4)//設定為4位元組對齊 
struct test 
{ 
char m1; 
double m4; 
int m3; 
}; 
#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。

再看下面這個例子:

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. #pragma pack(8)
  2. struct S1{  
  3.  char a;  
  4.  long b;  
  5. };   
  6. struct S2 {  
  7.  char c;  
  8.  struct S1 d;  
  9.  longlong e;  
  10. };  
  11. #pragma pack()
#pragma pack(8)
struct S1{
 char a;
 long b;
}; 

struct S2 {
 char c;
 struct S1 d;
 long long e;
};
#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.對齊後的長度必須是成員中最大的對齊引數的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊。

轉http://blog.csdn.NET/ce123_zhouwei/article/details/6971544

http://blog.csdn.Net/hairetz/article/details/4084088

http://www.cnblogs.com/cpoint/p/3369456.html

 未整理進來 http://blog.csdn.net/fanfank/article/details/12175585