大、小端模式的說法,來自喬納森·斯威夫特的小說《格列夫游記》,在小人國內(nèi)部分裂成 Big-endian 和 Little-endian 兩派,他們的爭論在于一派要求從雞蛋的大頭把雞蛋打破,另一派要求從雞蛋的小頭把雞蛋打破。斯威夫特借以諷刺英國的政黨之爭,而計(jì)算機(jī)工業(yè)則借此表示數(shù)據(jù)儲存順序的分歧。
大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中,這樣的存儲模式有點(diǎn)兒類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放;這和我們的閱讀習(xí)慣一致。
記憶方法: 地址的增長順序與值的增長順序相反。
小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中,這種存儲模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來,高地址部分權(quán)值高,低地址部分權(quán)值低,和我們的邏輯方法一致。
記憶方法: 地址的增長順序與值的增長順序相同。
那么,為什么會有大小端模式之分呢?
這是因?yàn)樵谟?jì)算機(jī)系統(tǒng)中,我們是以字節(jié)為單位的,每個地址單元都對應(yīng)著一個字節(jié),一個字節(jié)為 8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對于位數(shù)大于 8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節(jié),那么必然存在著一個如何將多個字節(jié)安排的問題。因此就導(dǎo)致了大端存儲模式和小端存儲模式。例如一個16bit的short型x,在內(nèi)存中的地址為0x0010,x的值為0x1122,那么0x11為高字節(jié),0x22為低字節(jié)。對于大端模式,就將0x11放在低地址(即0x0010)中,0x22放在高地址中(即0x0011)中。小端模式,剛好相反。我們常用的X86結(jié)構(gòu)是小端模式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以隨時在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令)進(jìn)行大小端的切換。
如此說來,大小端模式就是處理器在內(nèi)存中對多字節(jié)數(shù)據(jù)進(jìn)行存取的不同方式。注意兩點(diǎn):一是處理器,大小端模式是處理器的差異造成的;二是多字節(jié)數(shù)據(jù),也就是說只有處理器操作的數(shù)據(jù)類型超過一個字節(jié)時才會存在大小端模式的問題,而操作char類型數(shù)據(jù)是不存在該問題的。
字節(jié)序是指多字節(jié)數(shù)據(jù)在計(jì)算機(jī)內(nèi)存中存儲或者網(wǎng)絡(luò)傳輸時各字節(jié)的存儲順序。字節(jié)序與大小端模式的概念相似,我們上面所說的就是在計(jì)算機(jī)內(nèi)存中存儲的字節(jié)順序問題(即主機(jī)字節(jié)序),此外還存在網(wǎng)絡(luò)傳輸中的字節(jié)順序問題(即網(wǎng)絡(luò)字節(jié)序)。
那么,為什么會存在網(wǎng)絡(luò)字節(jié)序呢?
我們知道,網(wǎng)絡(luò)傳輸上的數(shù)據(jù)流是字節(jié)流,對于一個多字節(jié)數(shù)值,在進(jìn)行網(wǎng)絡(luò)傳輸?shù)臅r候,就需要考慮先傳遞哪個字節(jié)?也就是說,當(dāng)接收端收到第一個字節(jié)的時候,它是將這個字節(jié)作為高位還是低位來處理呢?實(shí)際上,接收端收到的第一個字節(jié)會被當(dāng)作高位看待,這就要求發(fā)送端發(fā)送的第一個字節(jié)應(yīng)當(dāng)是高位。而在發(fā)送端發(fā)送數(shù)據(jù)時,發(fā)送的第一個字節(jié)是該數(shù)字在內(nèi)存中起始地址對應(yīng)的字節(jié)。可見多字節(jié)數(shù)值在發(fā)送前,在內(nèi)存中數(shù)值應(yīng)該以大端模式存放。
網(wǎng)絡(luò)字節(jié)序是 TCP/IP 中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的 CPU 類型、操作系統(tǒng)等無關(guān),從而可以保證數(shù)據(jù)在不同主機(jī)之間傳輸時能夠被正確解釋。好了,我們需要記住,網(wǎng)絡(luò)字節(jié)序是一種規(guī)定,它采用的是大端模式(Big-endian)。
在網(wǎng)絡(luò)程序開發(fā)或是跨平臺開發(fā)時,就需要注意字節(jié)序的問題了。應(yīng)該保證只用一種字節(jié)序,否則兩方的解釋不一樣就會產(chǎn)生錯誤。為了方便開發(fā),BSD socket 提供了以下四個轉(zhuǎn)換函數(shù):
htons 把 unsigned short 類型從主機(jī)序轉(zhuǎn)換到網(wǎng)絡(luò)序
htonl 把 unsigned long 類型從主機(jī)序轉(zhuǎn)換到網(wǎng)絡(luò)序
ntohs 把 unsigned short 類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機(jī)序
ntohl 把 unsigned long 類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機(jī)序
在使用 Little-endian 的系統(tǒng)中,這些函數(shù)會把字節(jié)序進(jìn)行轉(zhuǎn)換;而在使用 Big-endian 的系統(tǒng)中,這些函數(shù)會定義成空宏。
看到這里,就會想:為什么只有 short 和 long 類型?對于 float 和 double類型怎么處理?要是遇到64位系統(tǒng)怎么辦?
有網(wǎng)友說:htons 和 htonl 是因?yàn)?TCP/IP 的一些參數(shù)用到 int 和 long,但沒有 float 和 double 的參數(shù)。另外,float 和 double,與 CPU 無關(guān)。一般編譯器是按照 IEEE 標(biāo)準(zhǔn)解釋的,即把 float/double 看作4/8個字符的數(shù)組進(jìn)行解釋。因此,只要編譯器是支持IEEE浮點(diǎn)標(biāo)準(zhǔn)的,就不需要考慮字節(jié)順序。
大家注意到,這里說不需要轉(zhuǎn)換,也是有條件的。我沒有考證過例外情況的存在比例,但是我相信在絕大部分情況下,編譯器都是符合IEEE標(biāo)準(zhǔn)的。如果你實(shí)在不放心,可以采取下面兩種辦法:
(1) 在保證不超過int范圍的情況下,將浮點(diǎn)數(shù)乘以100(或1000,10000,視所需精度隨你定)轉(zhuǎn)換為整數(shù)傳輸,在接收端再除以100,得到浮點(diǎn)數(shù)。
此外,我們還會聽到 Java字節(jié)序,Java字節(jié)序與網(wǎng)絡(luò)字節(jié)序一樣,采用大端模式。不是說大小端模式只跟 CPU 有關(guān)嗎?也就是說不同的計(jì)算機(jī)系統(tǒng)采用不同的字節(jié)序存儲數(shù)據(jù),一種 CPU 不是 Big-endian 就是Little-endian。那這個 Java字節(jié)序到底是什么鬼?
Java 程序員是幸福,因?yàn)橄鄬τ?C/C++ 的不跨平臺,JVM 為開發(fā)人員屏蔽了大量的底層細(xì)節(jié)和復(fù)雜性,從而能夠?qū)⒕Ψ旁趯?shí)現(xiàn)特定的業(yè)務(wù)邏輯上,因此 JVM 規(guī)定了數(shù)據(jù)的存儲順序(即大端模式)。所以做 Java 程序開發(fā)完全可以忽略字節(jié)序的存在,甚至有些 Java 程序員根本沒聽過字節(jié)序的概念。
想學(xué)習(xí)的你和我聯(lián)系預(yù)約就可以免費(fèi)聽課了。
宋工企鵝號:3524-6590-88 Tel/WX:173--1795--1908