前言
一般來說,底層開發人員接觸到這大小端儲存的概念比較多,特別是嵌入式開發人員。我們知道,不管什麼類型的作業系統都需要對資料或檔案進行存取操作,但由於各個系統的儲存方式會因為其CPU架構不同而有差異。對於所有CPU來說,它們大概存在兩種儲存方式:大端位元組序(big-endian),小端位元組序(little-endian)。
常見的CPU架構的位元組序吧:
Big Endian : PowerPC、IBM、Sun
Little Endian : x86、DEC
定義:
a) Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。
b) Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。
c) 網路位元組序:TCP/IP各層協議將位元組序定義為Big-Endian,因此TCP/IP協議中使用的位元組序通常稱之為網路位元組序。
記憶方法:
系統儲存資料的時候是以位元組為單位的,因此在處理short,int等大於1個位元組的資料類型時,就會可能存在儲存位置不一樣。系統申請記憶體都是從低位開始的,因此以在低位存的資料來判斷或命名該方式,所以低位存高位元據的方式叫大端位元組序,低位存低位就叫小端位元組序。這樣理解的話是不是方便記憶呢?
至於前面提到的網路位元組序,這個概念來自網路編程的一個術語,實際上它就是大端位元組序。呵呵,為什麼它非要是大端位元組序而不是小端位元組序呢?關於這個問題,我個人有這樣的一個理解,至於對不對大家自己去判斷吧,我的理解是這樣的:
網路位元組序處理的主要是通訊時的目標地址或源地址,即平時我們說的IP地址,它是一個4個位元組的數字類型(uint32_t).假設一個主機IP地址為192.68.1.1,那從高到低位它的資料位元是192 68 1 1,系統在讀資料的時候是從低到高開始讀取的。如果將IP按大端位元組序存取,則從低位地址到高位地址依次存放數字:192,68,1,1。這樣在取資料的時候剛好能組成最初的IP形式:192 68 1 1,因為網路地址IP需要的就是這四個數字這樣的形式,也方便將之轉換為字串。
儲存例子
下表是存一個4位元組的0x12345678,不會畫圖將就著看吧,地址是從低到高對應的表格從上到下:
| address |
big-endian |
little-endian |
| 0x0000 |
0x12 |
0x78 |
| 0x0004 |
0x34 |
0x56 |
| 0x0008 |
0x56 |
0x34 |
| 0x0012 |
0x78 |
0x12 |
通過這個表格的對比是不是比較清楚大小端的儲存方式了呢,那麼大小端位元組序的轉換需要怎麼做呢?很簡單,就是將高位地址裡的內容與低位地址內的內容兩兩交換就可以了。
如何判斷CPU採用何種位元組儲存順序(大小端)
常見的是聯合體判斷法,代碼如下:
1 bool isBigEndian() 2 { 3 union 4 { 5 int a; 6 char b; 7 }num; 8 9 num.a = 0x1234; 10 return ( num.b == 0x12 ) 11 }
大小端轉換
在面試的時候被問到了這個題,寫一個宏來對一個16位機進行大小端轉換操作,當時沒有寫出來,只是回答:將高低地址內容交換就OK了。後來想想其實這個問題很簡單,用移位的方法非常好實現,而且這樣做效率高,速度快。
移位原理比較簡單,相當於是將原資料移位後再保留對應的數位,將其它位置0,將每個位元組的內容都準備好後,最後再將它們相或就得到轉換後的資料了。
1 //16位機2 #define __SWP16(A) (( ((uint16)(A) & 0xff00) >> 8) | \ 3 (( (uint16)(A) & 0x00ff) << 8)) 4 5 //32位系統,大小端轉換6 #define __SWP32(A) ((( (uint32)(A) & 0xff000000) >> 24) | \ 7 (( (uint32)(A) & 0x00ff0000) >> 8) | \ 8 (( (uint32)(A) & 0x0000ff00) << 8) | \ 9 (( (uint32)(A) & 0x000000ff) << 24))