大端模式和小端模式初探
字節序模式由來
關於大端小端名詞的由來,有一個有趣的故事,來自於Jonathan Swift的《格利佛遊記》:Lilliput和Blefuscu這兩個強國在過去的36個月中一直在苦戰。戰爭的原因:大家都知道,吃雞蛋的時候,原始的方法是打破雞蛋較大的一端,可是那時的皇帝的祖父由於小時侯吃雞蛋,按這種方法把手指弄破了,因此他的父親,就下令,命令所有的子民吃雞蛋的時候,必須先打破雞蛋較小的一端,違令者重罰。然後老百姓對此法令極爲反感,期間發生了多次叛亂,其中一個皇帝因此送命,另一個丟了王位,產生叛亂的原因就是另一個國家Blefuscu的國王大臣煽動起來的,叛亂平息後,就逃到這個帝國避難。據估計,先後幾次有11000餘人情願死也不肯去打破雞蛋較小的端吃雞蛋。這個其實諷刺當時英國和法國之間持續的衝突。Danny Cohen一位網絡協議的開創者,第一次使用這兩個術語指代字節順序,後來就被大家廣泛接受。
什麼是大端和小端
大端模式(Big-Endian)
高位字節放置在內存的低地址端,低位字節放置在內存的高地址端
比如數字0x12345678
(12
是數字的高位字節,78
是數字的低位字節),大端模式的表示形式爲
低地址 >>>>>>>>>>>>>>>>> 高地址
0x12 | 0x34 | 0x56 | 0x78
小端模式(Little-Endian)
低位字節放置在內存的低地址端,高位字節放置在內存的高地址端
比如上述數字0x12345678
, 小端模式的表示形式爲
低地址 >>>>>>>>>>>>>>>>> 高地址
0x78 | 0x56 | 0x34 | 0x12
示例
16bit寬的數0x1234在Little-endian模式(以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)爲:
內存地址 | 小端模式存放內容 | 大端模式存放內容 |
---|---|---|
0x4000 | 0x34 | 0x12 |
0x4001 | 0x12 | 0x34 |
32bit寬的數0x12345678在Little-endian模式以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)爲:
內存地址 | 小端模式存放內容 | 大端模式存放內容 |
---|---|---|
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
優缺點
大端模式(Big-Endian):符號位在所表示的數據的內存的第一個字節中,便於快速判斷數據的正負和大小
小端模式(Little-Endian):
- 內存的低地址處存放低字節,所以在強制轉換數據時不需要調整字節的內容(註解:比如把int的4字節強制轉換成short的2字節時,就直接把int數據存儲的前兩個字節給short就行,因爲其前兩個字節剛好就是最低的兩個字節,符合轉換邏輯)
- CPU做數值運算時從內存中依順序依次從低位到高位取數據進行運算,直到最後刷新最高位的符號位,這樣的運算方式會更高效
爲什麼會有大小端之分呢
計算機系統中內存是以字節爲單位進行編址的,每個地址單元都唯一的對應着一個字節(8 bit)。這可以應對char
數據類型的存儲要求,因爲char
類型剛好1
個字節,但是有些類型的長度是多個字節的,比如2
字節的short
, 4
字節的int
等。因此這裏就出現瞭如何安排多個字節數據中各字節存放順序的問題,是高位字節放在低地址段呢?還是低位字節放在低地址段呢?正是不同的防止順序導致了大端模式和小端模式的出現。
如何判斷機器的字節序
既然已經了接了大端模式和小端模式只是兩種不同存儲字節的方式,那麼如果通過程序判斷機器的字節序呢,下面程序中給出兩種方式判斷電腦的字節序:
/*
* 方法一
* 通過將int類型強制轉換爲char類型,所以單字節的char類型變量取得了4字節的int類型變量的低地址部分,然後
* 通過判斷低地址部分的值獲取字節序。
*/
bool is_big_endian_1()
{
int a = 0x1234;
printf("%zd\n", sizeof(a));
printf("%x\n", a);
char b;
b = *(char *)&a;
if(0x12 == b)
return true;
return false;
}
/*
* 方法二
* 聯合體union的存放順序是所有成員都從低地址開始存放,然後獲取低地址部分來判斷機器字節序
*/
bool is_big_endian_2()
{
union NUM
{
int a;
char b;
} num;
num.a = 0x1234;
if(0x12 == num.b)
return true;
return false;
}
現狀
一般操作系統都是小端,而通訊協議是大端的。
常見CPU的字節序
- Big Endian : PowerPC、IBM、Sun
- Little Endian : x86、DEC
- ARM既可以工作在大端模式,也可以工作在小端模式。
常見文件的字節序
- Adobe PS – Big Endian
- BMP – Little Endian
- DXF(AutoCAD) – Variable
- GIF – Little Endian
- JPEG – Big Endian
- MacPaint – Big Endian
- RTF – Little Endian
注意 :Java和所有的網絡通訊協議都是使用Big-Endian的編碼。
主機字節序和網絡字節序
主機字節序
不同的CPU有不同的字節序,這些字節序決定了數據在內存中的保存順序,即主機字節序。主機字節序最常見的有兩種,即上面提到的:大端模式和小端模式。
網絡字節序
網絡字節序是TCP/IP
中規定的一種數據表示格式,它與具體的CPU類型、操作系統無關,從而可以保證數據在不同主機之間傳輸時能被正確解釋。網絡字節序採取大端模式。
Linux下進行網絡編程時,經常用到htons
和htonl
兩個函數,他們就是將主機字節序轉換成網絡字節序。
字節序轉換
大小端轉換
// 實現16bit的數據之間的大小端轉換
#define BLSWITCH16(A) ( ( ( (uint16)(A) & 0xff00 ) >> 8 ) | \
( ( (uint16)(A) & 0x00ff ) << 8 ) )
// 實現32bit的數據之間的大小端轉換
#define BLSWITCH32(A) ( ( ( (uint32)(A) & 0xff000000) >> 24) |\
(((uint32)(A) & 0x00ff0000) >> 8) | \
(((unit32)(A) & 0x0000ff00) << 8) | \
(((uint32)(A) & 0x000000ff) << 32) )
網絡字節序和主機字節序轉換
由於網絡字節序一律爲大端,而目前個人PC大部分都是X86的小端模式,因此網絡編程中不可避免得要進行網絡字節序和主機字節序之間的相互轉換,下面是 socket 提供的轉換函數
#define ntohs(n) // 16位數據類型網絡字節順序到主機字節順序的轉換
#define htons(n) // 16位數據類型主機字節順序到網絡字節順序的轉換
#define ntohl(n) // 32位數據類型網絡字節順序到主機字節順序的轉換
#define htonl(n) // 32位數據類型主機字節順序到網絡字節順序的轉換