數據在內存中的存儲方式( Big Endian和Little Endian的區別 )(x86系列則采用little endian方式存儲數據)


1.故事的起源 
“endian”這個詞出自《格列佛游記》。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。 
我們一般將endian翻譯成“字節序”,將big endian和little endian稱作“大尾”和“小尾”。 

2.什么是Big Endian和Little Endian? 
在設計計算機系統的時候,有兩種處理內存中數據的方法。一種叫為little-endian,存放在內存中最低位的數值是來自數據的最右邊部分(也就是數據的最低位部分)。 比如某些文件需要在不同平台處理,或者通過Socket通信。這方面我們可以借助ntohl(), ntohs(), htonl(), and htons()函數進行格式轉換, 
個人補充:一個操作數作htonl或ntohl結果不一定相同,當機器字節序跟網絡字節序剛好是僅僅big endian和little endian的區別時是相同的。 

3. 如何理解Big Endian和Little Endian  
舉個例子: 
int a = 1; 
a這個數本身的16進制表示是0x00 00 00 01 
在內存中怎么存儲呢? 
如果你的CPU是intel x86架構的(基本上就是通常我們說的奔騰cpu),那么就是0x01 0x00 0x00 0x00 , 這也就是所謂的little-endian, 低字節存放在內存的低位. 
如果你的CPU是老式AMD系列的(很老很老的那種,因為最新的AMD系列已經是x86架構了), 它的字節序就是big-endian, 其內存存儲就是 0x00 0x00 0x00 0x01在內存中從高字節開始存放。 
現在世界上絕大多數的CPU都是little-endian。 

4. 了解big-endian和little-endian有什么作用? 
一個重要的作用就是了解在網絡上不同的機器間的數據如何傳輸。 假設我們在網絡上有兩台機器A和B, 其中A為little-endian,B為big-endian 機器A要傳輸上面的整數a給機器B,如何傳輸呢? 過程是這樣的: 
機器A先把a在內存中的四個字節0x 01 0x00 0x00 0x00轉化為網絡字節序0x00 0x00 0x00 0x01,然后一個字節一個字節(從0x00到0x01)喂到網絡上去 ,然后機器B從網絡上一個字節一個字節地取出四個字節0x00 0x00 0x00 0x01后又會轉化為本地字節序 0x00 0x00 0x00 0x01后放入內存。因而B正確地得到了來自A的數據a 。如果數據缺少在網絡上的字節序轉換的話,情況會怎樣呢? 
機器A先把a由在內存的四個字節0x 01 0x00 0x00 0x00 一個字節一個字節地喂到網絡上,然后機器B從網絡上一個字節一個字節地收到0x 01 0x00 0x00 0x00並放入到內存中, B認為他收到了0x01000000, 也就是十進制數1677216,這顯然是錯誤的. 

5.如何判斷系統是Big Endian還是Little Endian? 
在/usr/include/中(包括子目錄)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),確定其值。這個值一般在endian.h或machine/endian.h文件中可以找到,有時在feature.h中,不同的操作系統可能有所不同。一般來說,Little Endian系統BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)為1234,Big Endian系統為4321。大部分用戶的操作系統(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本質上說,Little Endian還是Big Endian與操作系統和芯片類型都有關系。

 

 

ARM體系中,每個字單元包含4個字節單元或者兩個半字單元。在字單元中,4個字節哪一個是高位字節,哪一個是低位字節則有兩種不同的格式:big-endian和little-endian格式。在小端模式中,低位字節放在低地址,高位字節放在高地址;在大端模式中,低位字節放在高地址,高位字節放在低地址。

   如果將一個32位的整數0x12345678(如用UltraEdit打開某個文件看到的第一行頭四個字節是:"00000000h:12 34 56 78")存放到一個整型變量(int)中,這個整型變量(文件內容)采用大端或者小端模式在內存中的存儲由下表所示。

地址偏移

大端模式

小端模式

0x00

12

78

0x01

34

56

0x02

56

34

0x03

78

12

 

對於文件內容 0x12345678,把前面("12")的看為高端字節,后面("78")的看為低端字節,那么可以使用"高高低低"(Little Endian),"高低高低"(Big Endian)的口訣。直觀的區分,如果發現內存的內容和文件的內容在順序上以4個字節顛倒,那么他就是Little Edian。實現Big Endian和Little Endian主要是由編譯器指定的,通常是在CCFLAG 加參數,如: -DENDIAN_LITTLE,設定編譯為小端字節。實際中用Trace 32可以用Memory Dump查看內存內容,和寫入文件比較后判斷為大端還是小端。

  如果將一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在內存中的存儲在大小端模式由下表所示。

地址偏移

大端模式

小端模式

0x00

12

34

0x01

34

12

 

由上表所知,采用大小模式對數據進行存放的主要區別在於在存放的字節順序,大端方式將高位存放在低地址,小端方式將低位存放在低地址

  那么該如何判斷CPU是大端模式還是小端模式呢??

  在C語言中,聯合體union的存放順序是所有成員都從低地址開始存放的。利用這一特點,可以用聯合體變量判斷ARM或x86環境下,存儲系統是是大端還是小端模式。

  具體的代碼如下:

#include "stdio.h"
int main()
{
  union w
 {
  int a;  //4 bytes
  char b; //1 byte
 } c;
  c.a=1;
  if (c.b==1)
  printf("It is Little_endian!/n");
  else
  printf("It is Big_endian!/n");
  return 1;
}

 

 

數據在存放到內存里的時候,有兩種存放方式,即:Big Endian 和 Little Endian,這兩個存取方式決定了內存存放數據的原則是 高高低低 原則 還是 高低低高 原則。

高高低低--內存中的高位存放數據的高位,內存中的低位存放數據的低位(Little Endian 

高低低高--內存中的高位存放數據的低位,內存中的低位存放數據的高位(Big Endian 

比如:我有一個數據,是0xA5A1,它在存放到內存中是怎樣存放的呢?因為在我們平時的書寫中,A5是高位,A1在低位,存放到內存中的時候,A1存放在0x4000這個位置,而A5存放在0x4001這個位置,高位存放在內存的高地址中,低位存放在低地址中,這種方式就是Little Endian 。

下面再粘一篇網友的總結的文章來詳細闡述一下這兩種方式的區別:

                  Big Endian  和 Little Endian 模式的區別(轉載)

談到字節序的問題,必然牽涉到兩大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存儲數據,而x86系列則采用little endian方式存儲數據。那么究竟什么是big endian,什么又是little endian呢?

其實big endian是指低地址存放最高有效字節(MSB),而little endian則是低地址存放最低有效字節(LSB)。

    用文字說明可能比較抽象,下面用圖像加以說明。比如數字0x12345678在兩種不同字節序CPU中的存儲順序如下所示:

Big Endian

   低地址                                            高地址

 ----------------------------------------------------------------------------->

   |     12     |      34    |     56      |     78    |

Little Endian 

   低地址                                            高地址

   ----------------------------------------------------------------------------->

   |     78     |      56    |     34      |     12    |

     從上面兩圖可以看出,采用big endian方式存儲數據是符合我們人類的思維習慣的。

     為什么要注意字節序的問題呢?你可能這么問。當然,如果你寫的程序只在單機環境下面運行,並且不和別人的程序打交道,那么你完全可以忽略字節序的存在。但是,如果你的程序要跟別人的程序產生交互呢?尤其是當你把你在微機上運算的結果運用到計算機群上去的話。

 

在這里我想說說兩種語言。C/C++語言編寫的程序里數據存儲順序是跟編譯平台所在的CPU相關的,而JAVA編寫的程序則唯一采用big endian方式來存儲數據。試想,如果你用C/C++語言在x86平台下編寫的程序跟別人的JAVA程序互通時會產生什么結果?就拿上面的0x12345678來說,你的程序傳遞給別人的一個數據,將指向0x12345678的指針傳給了JAVA程序,由於JAVA采取big endian方式存儲數據,很自然的它會將你的數據翻譯為0x78563412。因此,在你的C程序傳給JAVA程序之前有必要進行字節序的轉換工作。

 

所有網絡協議也都是采用big endian的方式來傳輸數據的。所以有時我們也會把big endian方式稱之為網絡字節序。當兩台采用不同字節序的主機通信時,在發送數據之前都必須經過字節序的轉換成為網絡字節序后再進行傳輸。ANSI C中提供了下面四個轉換字節序的宏。


一道C語言的試題:請寫一個C函數,若處理器是Big_endian的,則返回0;若是Little_endian的,則返回1。

解答:

int checkCPU()
{
 {
  union w
  {
   int a;
   char b;
  } c;
  c.a = 1;
  return (c.b == 1);
 }
}

嵌入式系統開發者應該對Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU對操作數的存放方式是從低字節到高字節,而Big-endian模式對操作數的存放方式是從高字節到低字節。例如,16bit寬的數0x1234在Little- endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)為:

內存地址 

存放內容 

0x4000

0x34

0x400 

0x12
  而在Big-endian模式CPU內存中的存放方式則為:

 

內存地址

存放內容

0x4000

0x12

0x4001

0x34


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

內存地址

存放內容

0x4000

0x78

0x4001

0x56

0x4002

0x34

0x4003

0x12


  而在Big-endian模式CPU內存中的存放方式則為:

內存地址

存放內容

0x4000

0x12

0x4001

0x34

0x4002

0x56

0x4003

0x78


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM