1. 程式人生 > >位元組序問題--大端法小端法

位元組序問題--大端法小端法

一、位元組序定義

位元組序,顧名思義位元組的順序,再多說兩句就是大於一個位元組型別的資料在記憶體中的存放順序(一個位元組的資料當然就無需談順序的問題了)。

其實大部分人在實際的開發中都很少會直接和位元組序打交道。唯有在跨平臺以及網路程式中位元組序才是一個應該被考慮的問題。

在所有的介紹位元組序的文章中都會提到位元組序分為兩類:Big-Endian和Little-Endian。引用標準的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。
b) Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。
c) 網路位元組序:4個位元組的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit。這種傳輸次序稱作大端位元組序。由於TCP/IP首部中所有的二進位制整數在網路中傳輸時都要求以這種次序,因此它又稱作網路位元組序。比如,乙太網頭部中2位元組的“乙太網幀型別”,表示後面資料的型別。對於ARP請求或應答的乙太網幀型別來說,在網路傳輸時,傳送的順序是0x08,0x06。在記憶體中的映象如下圖所示:
棧底 (高地址)
---------------
0x06 -- 低位 
0x08 -- 高位
---------------
棧頂 (低地址)
該欄位的值為0x0806。按照大端方式存放在記憶體中。

二、高/低地址與高低位元組

首先我們要知道我們C程式映像中記憶體的空間佈局情況:在《C專家程式設計》中或者《Unix環境高階程式設計》中有關於記憶體空間佈局情況的說明,大致如下圖:
----------------------- 最高記憶體地址 0xffffffff
 | 棧底
 .
 .              棧
 .
  棧頂
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的資料
----------------(統稱資料段)
初始化的資料
-----------------------
正文段(程式碼段)
----------------------- 最低記憶體地址 0x00000000

以上圖為例如果我們在棧上分配一個unsigned char buf[4],那麼這個陣列變數在棧上是如何佈局的呢[注1]?看下圖:
棧底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
棧頂 (低地址)

現在我們弄清了高低地址,接著來弄清高/低位元組,如果我們有一個32位無符號整型0x12345678(呵呵,恰好是把上面的那4個位元組buf看成一個整型),那麼高位是什麼,低位又是什麼呢?其實很簡單。在十進位制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進位制也是如此。就拿0x12345678來說,從高位到低位的位元組依次是0x12、0x34、0x56和0x78。

高低地址和高低位元組都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,並用圖示說明兩種位元組序:
以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) -- 低位
---------------
棧頂 (低地址)

在現有的平臺上Intel的X86採用的是Little-Endian,而像Sun的SPARC採用的就是Big-Endian。

三、例子

嵌入式系統開發者應該對Little-endian和Big-endian模式非常瞭解。採用Little-endian模式的CPU對運算元的存放方式是從低位元組到高位元組,而Big-endian模式對運算元的存放方式是從高位元組到低位元組。

例如,16bit寬的數0x1234在Little-endian模式CPU記憶體中的存放方式(假設從地址0x4000開始存放)為:

記憶體地址  存放內容
 0x4001    0x12
 0x4000    0x34

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

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

記憶體地址  存放內容
 0x4003     0x12
 0x4002     0x34
 0x4001     0x56
 0x4000     0x78
 
而在Big-endian模式CPU記憶體中的存放方式則為:

記憶體地址  存放內容
 0x4003     0x78
 0x4002     0x56
 0x4001     0x34
 0x4000     0x12

關於位元組序(大端法、小端法)的定義

《UNXI網路程式設計》定義:術語“小端”和“大端”表示多位元組值的哪一端(小端或大端)儲存在該值的起始地址。小端存在起始地址,即是小端位元組序;大端存在起始地址,即是大端位元組序。

也可以說:
1.小端法(Little-Endian)就是低位位元組排放在記憶體的低地址端即該值的起始地址,高位位元組排放在記憶體的高地址端。
2.大端法(Big-Endian)就是高位位元組排放在記憶體的低地址端即該值的起始地址,低位位元組排放在記憶體的高地址端。

舉個簡單的例子,對於整形0x12345678。它在大端法和小端法的系統內中,分別如圖1所示的方式存放。

網路位元組序

我們知道網路上的資料流是位元組流,對於一個多位元組數值,在進行網路傳輸的時候,先傳遞哪個位元組?也就是說,當接收端收到第一個位元組的時候,它是將這個位元組作為高位還是低位來處理呢?
網路位元組序定義:收到的第一個位元組被當作高位看待,這就要求傳送端傳送的第一個位元組應當是高位。而在傳送端傳送資料時,傳送的第一個位元組是該數字在記憶體中起始地址對應的位元組。可見多位元組數值在傳送前,在記憶體中數值應該以大端法存放。
網路位元組序說是大端位元組序。
比如我們經過網路傳送0x12345678這個整形,在80X86平臺中,它是以小端法存放的,在傳送前需要使用系統提供的htonl將其轉換成大端法存放,如圖2所示。

位元組序測試程式

不同cpu平臺上位元組序通常也不一樣,下面寫個簡單的C程式,它可以測試不同平臺上的位元組序。

#include <stdio.h>

#include <netinet/in.h>

int main()

{

    int i_num = 0x12345678;

    printf("[0]:0x%x\n", *((char *)&i_num + 0));

    printf("[1]:0x%x\n", *((char *)&i_num + 1));

    printf("[2]:0x%x\n", *((char *)&i_num + 2));

    printf("[3]:0x%x\n", *((char *)&i_num + 3));

10 

11 

    i_num = htonl(i_num);

12 

    printf("[0]:0x%x\n", *((char *)&i_num + 0));

13 

    printf("[1]:0x%x\n", *((char *)&i_num + 1));

14 

    printf("[2]:0x%x\n", *((char *)&i_num + 2));

15 

    printf("[3]:0x%x\n", *((char *)&i_num + 3));

16 

17 

    return 0;

18 


在80X86CPU平臺上,執行該程式得到如下結果:
[0]:0x78
[1]:0x56
[2]:0x34
[3]:0x12

[0]:0x12
[1]:0x34
[2]:0x56
[3]:0x78

分析結果,在80X86平臺上,系統將多位元組中的低位儲存在變數起始地址,使用小端法。htonl將i_num轉換成網路位元組序,可見網路位元組序是大端法。

大端(Big Endian)與小端(Little Endian)簡介

///////////////////////////////////////////////////////

1. 你從哪裡來?

端模式(Endian)的這個詞出自Jonathan Swift書寫的《格列佛遊記》。這本書根據將雞蛋敲開的方法不同將所有的人分為兩類,從圓頭開始將雞蛋敲開的人被歸為Big Endian,從尖頭開始將雞蛋敲開的人被歸為Littile Endian。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。在計算機業Big Endian和Little Endian也幾乎引起一場戰爭。在計算機業界,Endian表示資料在儲存器中的存放順序。採用大端方式進行資料存放符合人類的正常思維,而採用小端方式進行資料存放利於計算機處理。下文舉例說明在計算機中大小端模式的區別。

//////////////////////////////////////////////////////

2. 讀書百遍其義自見

小埠訣: 高高低低 -> 高位元組在高地址, 低位元組在低地址

大埠訣: 高低低高 -> 高位元組在低地址, 低位元組在高地址

long test = 0x313233334;

小端機器:

低地址 --> 高地址

00000010: 34 33 32 31         -> 4321

大端機器:

低地址 --> 高地址

00000010: 31 32 33 34         -> 4321

test變數儲存的是的0x10這個地址,

那編譯器怎麼知道是讀四個位元組呢? -> 根據變數test的型別long可知這個變數佔據4個位元組.

那編譯器怎麼讀出這個變數test所代表的值呢? -> 這就根據是little endian還是big endian來讀取

所以, 小端, 其值為0x31323334; 大端, 其值為0x34333231

htonl(test) 的情況:     ->其值為: 0x34333231

小端機器:

00000010: 31 32 33 34         -> 1234

大端機器:

00000010: 34 33 32 31         -> 4321

/////////////////////////////////////////////////////////////////////////////////////

3. 拿來主義

Byte Endian是指位元組在記憶體中的組織,所以也稱它為Byte Ordering,或Byte Order。

     對於資料中跨越多個位元組的物件, 我們必須為它建立這樣的約定:

(1) 它的地址是多少?

(2) 它的位元組在記憶體中是如何組織的?

    針對第一個問題,有這樣的解釋:

    對於跨越多個位元組的物件,一般它所佔的位元組都是連續的,它的地址等於它所佔位元組最低地址。(連結串列可能是個例外, 但連結串列的地址可看作連結串列頭的地址)。

    比如: int x,它的地址為0x100。 那麼它佔據了記憶體中的Ox100, 0x101, 0x102, 0x103這四個位元組(32位系統,所以int佔用4個位元組)。

    上面只是記憶體位元組組織的一種情況: 多位元組物件在記憶體中的組織有一般有兩種約定。 考慮一個W位的整數。

    它的各位表達如下:[Xw-1, Xw-2, ... , X1, X0],它的

    MSB (Most Significant Byte, 最高有效位元組)為 [Xw-1, Xw-2, ... Xw-8];

    LSB (Least Significant Byte, 最低有效位元組)為 [X7,X6,..., X0]。

    其餘的位元組位於MSB, LSB之間。

LSB和MSB誰位於記憶體的最低地址,即誰代表該物件的地址?

這就引出了大端(Big Endian)與小端(Little Endian)的問題。

如果LSB在MSB前面,既LSB是低地址, 則該機器是小端; 反之則是大端。

DEC (Digital Equipment Corporation,現在是Compaq公司的一部分)和Intel的機器(X86平臺)一般採用小端。

IBM, Motorola(Power PC), Sun的機器一般採用大端。

當然,這不代表所有情況。有的CPU即能工作於小端, 又能工作於大端,比如ARM, Alpha,摩托羅拉的PowerPC。 具體情形參考處理器手冊。

具體這類CPU是大端還是小端,應該和具體設定有關。

(如,Power PC支援little-endian位元組序,但在預設配置時是big-endian位元組序)

一般來說,大部分使用者的作業系統(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。

所以說,Little Endian還是Big Endian與作業系統和晶片型別都有關係。因此在一個處理器系統中,有可能存在大端和小端模式同時存在的現象。這一現象為系統的軟硬體設計帶來了不小的麻煩,這要求系統設計工程師,必須深入理解大端和小端模式的差別。大端與小端模式的差別體現在一個處理器的暫存器,指令集,系統匯流排等各個層次中。

Linux系統中,你可以在/usr/include/中(包括子目錄)查詢字串BYTE_ORDER(或

_BYTE_ORDER, __BYTE_ORDER),確定其值。BYTE_ORDER中文稱為位元組序。這個值一般在endian.h或machine/endian.h檔案中可以找到,有時在feature.h中,不同的作業系統可能有所不同。

【用函式判斷系統是Big Endian還是Little Endian】

enum {FALSE = 0, TRUE = !FALSE};

typedef short BOOL;

BOOL IsBig_Endian()

//如果位元組序為big-endian,返回true;

//反之為   little-endian,返回false

{

    unsigned short test = 0x1122;

    if(*( (unsigned char*) &test ) == 0x11)

       return TRUE;

else

    return FALSE;

}//IsBig_Endian()

//////////////////////////////////////////////////////////////////////////////

可以做個實驗

在windows上下如下程式

#include <stdio.h>

#include <assert.h>

void main( void )

{

        short test;

        FILE* fp;

        test = 0x3132; //(31ASIIC碼的’1’,32ASIIC碼的’2’)

        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)

              assert(0);

        fwrite(&test, sizeof(short), 1, fp);

        fclose(fp);

}

    然後在C盤下開啟test.txt檔案,可以看見內容是21,而test等於0x3132,可以明顯的看出來x86的位元組順序是低位在前.如果我們把這段同樣的程式碼放到(big-endian)的機器上執行,那麼打出來的檔案就是12.這在本機中使用是沒有問題的.但當你把這個檔案從一個big- endian機器複製到一個little-endian機器上時就出現問題了.

    如上述例子,我們在big-endian的機器上建立了這個test檔案,把其複製到little-endian的機器上再用fread讀到一個 short裡面,我們得到的就不再是0x3132而是0x3231了,這樣讀到的資料就是錯誤的,所以在兩個位元組順序不一樣的機器上傳輸資料時需要特別小心位元組順序,理解了位元組順序在可以幫助我們寫出移植行更高的程式碼.

正因為有位元組順序的差別,所以在網路傳輸的時候定義了所有位元組順序相關的資料都使用big-endian,BSD的程式碼中定義了四個巨集來處理:

#define ntohs(n)     //網路位元組順序到主機位元組順序 n代表net, h代表host, s代表short

#define htons(n)     //主機位元組順序到網路位元組順序 n代表net, h代表host, s代表short

#define ntohl(n)      //網路位元組順序到主機位元組順序 n代表net, h代表host, s代表 long

#define htonl(n)      //主機位元組順序到網路位元組順序 n代表net, h代表host, s代表 long

舉例說明下這其中一個巨集的實現:

#define sw16(x) \

    ((short)( \

        (((short)(x) & (short)0x00ffU) << 8) | \

        (((short)(x) & (short)0xff00U) >> 8) ))

這裡實現的是一個交換兩個位元組順序.其他幾個巨集類似.

我們改寫一下上面的程式

#include <stdio.h>

#include <assert.h>

#define sw16(x) \

    ((short)( \

        (((short)(x) & (short)0x00ffU) << 8) | \

        (((short)(x) & (short)0xff00U) >> 8) ))

#define sw32(x) \

((long)( \

   (((long)(x) & (long)0x000000ff) << 24) | \

   (((long)(x) & (long)0x0000ff00) << 8) | \

   (((long)(x) & (long)0x00ff0000) >> 8) | \

   (((long)(x) & (long)0xff000000) >> 24) ))

// 因為x86下面是低位在前,需要交換一下變成網路位元組順序

#define htons(x) sw16(x)

#define htonl(x) sw32(x)

void main( void )

{

        short test;

        FILE* fp;

        test = htons(0x3132); //(31ASIIC碼的’1’,32ASIIC碼的’2’)

        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)

              assert(0);

        fwrite(&test, sizeof(short), 1, fp);

        fclose(fp);

}

    如果在高位元組在前的機器上,由於與網路位元組順序一致,所以我們什麼都不幹就可以了,只需要把#define htons(x) sw16(x)巨集替換為 #define htons(x) (x).

    一開始我在理解這個問題時,總在想為什麼其他資料不用交換位元組順序?比如說我們write一塊buffer到檔案,最後終於想明白了,因為都是 unsigned char型別一個位元組一個位元組的寫進去,這個順序是固定的,不存在位元組順序的問題

【大端(Big Endian)與小端(Little Endian)簡介】

Byte Endian是指位元組在記憶體中的組織,所以也稱它為Byte Ordering,或Byte Order。

     對於資料中跨越多個位元組的物件, 我們必須為它建立這樣的約定:

(1) 它的地址是多少?

(2) 它的位元組在記憶體中是如何組織的?

    針對第一個問題,有這樣的解釋:

    對於跨越多個位元組的物件,一般它所佔的位元組都是連續的,它的地址等於它所佔位元組最低地址。(連結串列可能是個例外, 但連結串列的地址可看作連結串列頭的地址)。

    比如: int x,它的地址為0x100。 那麼它佔據了記憶體中的Ox100, 0x101, 0x102, 0x103這四個位元組(32位系統,所以int佔用4個位元組)。

    上面只是記憶體位元組組織的一種情況: 多位元組物件在記憶體中的組織有一般有兩種約定。 考慮一個W位的整數。

    它的各位表達如下:[Xw-1, Xw-2, ... , X1, X0],它的

    MSB (Most Significant Byte, 最高有效位元組)為 [Xw-1, Xw-2, ... Xw-8];

    LSB (Least Significant Byte, 最低有效位元組)為 [X7,X6,..., X0]。

    其餘的位元組位於MSB, LSB之間。

LSB和MSB誰位於記憶體的最低地址,即誰代表該物件的地址?

這就引出了大端(Big Endian)與小端(Little Endian)的問題。

如果LSB在MSB前面,既LSB是低地址, 則該機器是小端; 反之則是大端。

DEC (Digital Equipment Corporation,現在是Compaq公司的一部分)和Intel的機器(X86平臺)一般採用小端。

IBM, Motorola(Power PC), Sun的機器一般採用大端。

當然,這不代表所有情況。有的CPU即能工作於小端, 又能工作於大端,比如ARM, Alpha,摩托羅拉的PowerPC。 具體情形參考處理器手冊。

具體這類CPU是大端還是小端,應該和具體設定有關。

(如,Power PC支援little-endian位元組序,但在預設配置時是big-endian位元組序)

一般來說,大部分使用者的作業系統(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。

所以說,Little Endian還是Big Endian與作業系統和晶片型別都有關係。

Linux系統中,你可以在/usr/include/中(包括子目錄)查詢字串BYTE_ORDER(或

_BYTE_ORDER, __BYTE_ORDER),確定其值。BYTE_ORDER中文稱為位元組序。這個值一般在endian.h或machine/endian.h檔案中可以找到,有時在feature.h中,不同的作業系統可能有所不同。

          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相關的,而 J***A編寫的程式則唯一採用big endian方式來儲存資料。試想,如果你用C/C++語言在x86平臺下編寫的程式跟別人的J***A程式互通時會產生什麼結果?就拿上面的 0x12345678來說,你的程式傳遞給別人的一個數據,將指向0x12345678的指標傳給了J***A程式,由於J***A採取big endian方式儲存資料,很自然的它會將你的資料翻譯為0x78563412。什麼?竟然變成另外一個數字了?是的,就是這種後果。因此,在你的C程式傳給J***A程式之前有必要進行位元組序的轉換工作。

     無獨有偶,所有網路協議也都是採用big endian的方式來傳輸資料的。所以有時我們也會把big endian方式稱之為網路位元組序。當兩臺採用不同位元組序的主機通訊時,在傳送資料之前都必須經過位元組序的轉換成為網路位元組序後再進行傳輸。ANSI C中提供了下面四個轉換位元組序的巨集。

·BE和LE一文的補完

        我在8月9號的《Big Endian和Little Endian》一文中談了位元組序的問題,原文見上面的超級連結。可是有朋友仍然會問,CPU儲存一個位元組的資料時其位元組內的8個位元之間的順序是否也有 big endian和little endian之分?或者說是否有位元序的不同?

     實際上,這個位元序是同樣存在的。下面以數字0xB4(10110100)用圖加以說明。

Big Endian

   msb                                                         lsb

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

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |   1 |   0 |   1 |   1 |   0 |   1 |   0 |   0 |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

   lsb                                                         msb

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

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |   0 |   0 |   1 |   0 |   1 |   1 |   0 |   1 |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

     實際上,由於CPU儲存資料操作的最小單位是一個位元組,其內部的位元序是什麼樣對我們的程式來說是一個黑盒子。也就是說,你給我一個指向0xB4這個數的指標,對於big endian方式的CPU來說,它是從左往右依次讀取這個數的8個位元;而對於little endian方式的CPU來說,則正好相反,是從右往左依次讀取這個數的8個位元。而我們的程式通過這個指標訪問後得到的數就是0xB4,位元組內部的位元序對於程式來說是不可見的,其實這點對於單機上的位元組序來說也是一樣的。

     那可能有人又會問,如果是網路傳輸呢?會不會出問題?是不是也要通過什麼函式轉換一下位元序?嗯,這個問題提得很好。假設little endian方式的CPU要傳給big endian方式CPU一個位元組的話,其本身在傳輸之前會在本地就讀出這個8位元的數,然後再按照網路位元組序的順序來傳輸這8個位元,這樣的話到了接收端不會出現任何問題。而假如要傳輸一個32位元的數的話,由於這個數在littel endian方儲存時佔了4個位元組,而網路傳輸是以位元組為單位進行的,little endian方的CPU讀出第一個位元組後傳送,實際上這個位元組是原數的LSB,到了接收方反倒成了MSB從而發生混亂。

【用函式判斷系統是Big Endian還是Little Endian】

bool IsBig_Endian()

//如果位元組序為big-endian,返回true;

//反之為   little-endian,返回false

{

    unsigned short test = 0x1122;

    if(*( (unsigned char*) &test ) == 0x11)

       return TRUE;

else

    return FALSE;

}//IsBig_Endian()

三、例子

嵌入式系統開發者應該對Little-endian和Big-endian模式非常瞭解。採用Little- endian模式的CPU對運算元的存放方式是從低位元組到高位元組,而Big-endian模式對運算元的存放方式是從高位元組到低位元組。

例如,16bit寬的數0x1234在Little-endian模式CPU記憶體中的存放方式(假設從地址 0x4000開始存放)為:

記憶體地址 存放內容

0x4001    0x12

0x4000    0x34

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

記憶體地址 存放內容

0x4001    0x34

0x4000    0x12

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

記憶體地址 存放內容

0x4003     0x12

0x4002     0x34

0x4001     0x56

0x4000     0x78

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

記憶體地址 存放內容

0x4003     0x78

0x4002     0x56

0x4001     0x34

0x4000     0x12

三、例子

測試平臺 : Sun SPARC Solaris 9 和 Intel X86 Solaris 9

我們的例子是這樣的:在使用不同位元組序的平臺上使用相同的程式讀取同一個二進位制檔案的內容。

生成二進位制檔案的程式如下 :

int main() {

        FILE    *fp = NULL;

        int     value = 0x12345678;

        int     rv = 0;

        fp = fopen("temp.dat", "wb");

        if (fp == NULL) {

                printf("fopen error\n");

                return -1;

        }

        rv = fwrite(&value, sizeof(value), 1, fp);

        if (rv != 1) {

                printf("fwrite error\n");

                return -1;

        }

        fclose(fp);

        return 0;

}

讀取二進位制檔案的程式如下:

int main() {

        int             value   = 0;

        FILE         *fp     = NULL;

        int             rv      = 0;

        unsigned        char buf[4];

        fp = fopen("temp.dat", "rb");

        if (fp == NULL) {

                printf("fopen error\n");

                return -1;

        }

        rv = fread(buf, sizeof(unsigned char), 4, fp);

        if (rv != 4) {

                printf("fread error\n");

                return -1;

        }

        memcpy(&value, buf, 4); // or value = *((int*)buf);

        printf("the value is %x\n", value);

        fclose(fp);

        return 0;

}

測試過程:

(1) 在 SPARC 平臺下生成 temp.dat 檔案

在 SPARC 平臺下讀取 temp.dat 檔案的結果:

the value is 12345678

在 X86 平臺下讀取 temp.dat 檔案的結果:

the value is 78563412

(1) 在 X86 平臺下生成 temp.dat 檔案

在 SPARC 平臺下讀取 temp.dat 檔案的結果:

the value is 78563412

在 X86 平臺下讀取 temp.dat 檔案的結果:

the value is 12345678

[ 注 1]

buf[4] 在棧的佈局我也是通過例子程式得到的:

int main() {

        unsigned char buf[4];

        printf("the buf[0] addr is %x\n", buf);

        printf("the buf[1] addr is %x\n", &buf[1]);

        return 0;

}

output:

SPARC 平臺:

the buf[0] addr is ffbff788

the buf[1] addr is ffbff789

X86 平臺:

the buf[0] addr is 8047ae4

the buf[1] addr is 8047ae5

兩個平臺都是 buf[x] 所在地址高於 buf[y] (x > y) 。

如何判斷系統是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與作業系統和晶片型別都有關係。

Processor OS Order

x86 (Intel, AMD, … ) All little-endian

DEC Alpha All little-endian

HP-PA NT little-endian

HP-PA UNIX big-endian

SUN SPARC All? big-endian

MIPS NT little-endian

MIPS UNIX big-endian

PowerPC NT little-endian

PowerPC non-NT big-endian

RS/6000 UNIX big-endian

Motorola m68k All big-endian