CFSwapInt32HostToBig() 官方文檔解析為將一個32位整數從主機的本機字節順序大端格式 網絡數據是大端字節序??蛻艨赡苁谴蠖俗止?例如PowerPC Mac)或小端字節序(例如x86 Mac)。因此確保你的代碼字節順序處理小尾數法)的平臺上(例如x86)——這是一個空操作當然對大整數。
大端(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 -> 4321test變量存儲的是的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》一文中談了字節序的問題,原文見上面的超級鏈接??墒怯信笥讶匀粫枺珻PU存儲一個字節的數據時其字節內的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()一、字節序定義
字節序,顧名思義字節的順序,再多說兩句就是大于一個字節類型的數據在內存中的存放順序(一個字節的數據當然就無需談順序的問題了)。
其實大部分人在實際的開發中都很少會直接和字節序打交道。唯有在跨平臺以及網絡程序中字節序才是一個應該被考慮的問題。
在所有的介紹字節序的文章中都會提到字節序分為兩類: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
三、例子 測試平臺 : Sun SPARC Solaris 9 和 Intel X86 Solaris 9我們的例子是這樣的:在使用不同字節序的平臺上使用相同的程序讀取同一個二進制文件的內容。 生成二進制文件的程序如下 :/* gen_binary.c */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 ffbff788the buf[1] addr is ffbff789X86 平臺: the buf[0] addr is 8047ae4the buf[1] addr is 8047ae5
兩個平臺都是 buf[x] 所在地址高于 buf[y] (x > y) 。
在/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
新聞熱點
疑難解答