大端与小端详细介绍
一、什么是大端和小端
所謂的大端模式,就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
所謂的小端模式,就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
簡單來說:大端——高尾端,小端——低尾端
舉個例子,比如數字 0x12 34 56 78在內存中的表示形式為:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可見,大端模式和字符串的存儲模式類似。
3)下面是兩個具體例子:
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
4)大端小端沒有誰優誰劣,各自優勢便是對方劣勢:
小端模式 :強制轉換數據不需要調整字節內容,1、2、4字節的存儲方式一樣。
大端模式 :符號位的判定固定為第一個字節,容易判斷正負。
二、數組在大端小端情況下的存儲:
以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) – 低位
--------------
低地址
三、為什么會有大小端模式之分呢?
這是因為在計算機中,我們是以字節為單位的,每個地址單元都對應著一個字節,一個字節為 8 bit。但是在C 語言中除了 8 bit 的char之外,還有 16 bit 的 short型,32bit的long型(要看具體的編譯器),另外,對于位數大于8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節,那么必然存在著一個如果將多個字節安排的問題。因此就導致了大端存儲模式和小端存儲模式。例如一個16bit的short型 x ,在內存中的地址為 0x0010,x 的值為0x1122,那么0x11位高字節,0x22位低字節。對于大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反。我們常用的X86結構是小端模式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬件來選擇是大端模式還是小端模式。
四、如何判斷機器的字節序 (重點)
一般都是通過 union 來測試的,下面這段代碼可以用來測試一下你的編譯器是大端模式還是小端模式:
#include <stdio.h>
int main (void)
{
union
{
short i;
char a[2];
}u;
u.a[0] = 0x11;
u.a[1] = 0x22;
printf (“0x%x\n”, u.i); //0x2211 為小端 0x1122 為大端
return 0;
}
輸出結果:
0x2211
union 型數據所占的空間等于其最大的成員所占的空間。對 union 型的成員的存取都是相對于該聯合體基地址的偏移量為 0 處開始,也就是聯合體的訪問不論對哪個變量的存取都是從 union 的首地址位置開始。
聯合是一個在同一個存儲空間里存儲不同類型數據的數據類型。這些存儲區的地址都是一樣的,聯合里不同存儲區的內存是重疊的,修改了任何一個其他的會受影響。
參看:C語言再學習 – 結構和其他數據形式
共用體(參考“共用體”百科詞條)是一種特殊形式的變量,使用關鍵字union來定義
共用體(有些人也叫"聯合")聲明和共用體變量定義與結構體十分相似。其形式為:
union 共用體名{
數據類型 成員名;
數據類型 成員名;
…
} 變量名;
參看:
共用體表示幾個變量共用一個內存位置,在不同的時間保存不同的數據類型和不同長度的變量。在union中,所有的共用體成員共用一個空間,并且同一時間只能儲存其中一個成員變量的值。
下例表示聲明一個共用體foo:
union foo{/“共用”類型“FOO”/
int i; /“整數”類型“i”/
char c; /“字符”類型“C”/
double k; /“雙”精度類型“K”/
};
再用已聲明的共用體可定義共用體變量。例如,用上面說明的共用體定義一個名為bar的共用體變量, 可寫成:
union foo bar;
在共用體變量bar中, 整型變量 i 和字符變量 c 共用同一內存位置。
當一個共用體被聲明時, 編譯程序自動地產生一個變量, 其長度為聯合中最大的變量長度的整數倍。以上例而言,最大長度是double數據類型,所以foo的內存空間就是double型的長度。
union foo/“共用”類型“FOO”/
{
char s[10]; /“字符”類型的數組“S”下面有“10”個元素/
int i; /“整數”類型i/
};
在這個union中,foo的內存空間的長度為12,是int型的3倍,而并不是數組的長度10。若把int改為double,則foo的內存空間為16,是double型的兩倍。
1)共用體和結構體都是由多個不同的數據類型成員組成, 但在任何同一時刻, 共用體只存放了一個被選中的成員, 而結構體的所有成員都存在。
2.)對于共用體的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對于結構體的不同成員賦值是互不影響的。
總結:
恍然大悟,union 聯合之前還是沒有理解透。一開始不太理解,為什么給 a[0]、a[1] 賦值,i 沒有定義啊,為什么會有值呢,或者值為什么不是隨機數呢?現在明白了,我們為什么用 union 聯合來測試大小端,在聯合變量 u 中, 短整型變量 i 和字符數組 a 共用同一內存位置。給 a[0]、a[1] 賦值后,i 也是從同一內存地址讀值的。
知道這層關系后,那么通過強制類型轉換,判斷其實存儲位置,也可以測試大小端了:
#include <stdio.h>
int main (void)
{
short i = 0x1122;
char a = (char)(&i);
printf (“0x%x\n”, *(a + 0)); //大端為 0x11 小端為 0x22
printf (“0x%x\n”, *(a + 1));
return 0;
}
輸出結果:
0x22
0x11
說明:上面兩個例子,可以通過 if 語句來判斷大小端,這里只是介紹方法。
五、常見的字節序
一般操作系統都是小端,而通訊協議是大端的。
1)常見CPU的字節序
Big Endian : PowerPC、IBM、Sun
Little Endian : x86、DEC
ARM既可以工作在大端模式,也可以工作在小端模式。
2)常見文件的字節序
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的編碼。
六、如何進行大小端轉換(重點)
第一種方法:位操作
#include<stdio.h>
typedef unsigned int uint_32 ;
typedef unsigned short uint_16 ;
//16位
#define BSWAP_16(x)
(uint_16)((((uint_16)(x) & 0x00ff) << 8) |
(((uint_16)(x) & 0xff00) >> 8)
)
//32位
#define BSWAP_32(x)
(uint_32)((((uint_32)(x) & 0xff000000) >> 24) |
(((uint_32)(x) & 0x00ff0000) >> 8) |
(((uint_32)(x) & 0x0000ff00) << 8) |
(((uint_32)(x) & 0x000000ff) << 24)
)
//無符號整型16位
uint_16 bswap_16(uint_16 x)
{
return (((uint_16)(x) & 0x00ff) << 8) |
(((uint_16)(x) & 0xff00) >> 8) ;
}
//無符號整型32位
uint_32 bswap_32(uint_32 x)
{
return (((uint_32)(x) & 0xff000000) >> 24) |
(((uint_32)(x) & 0x00ff0000) >> 8) |
(((uint_32)(x) & 0x0000ff00) << 8) | \
(((uint_32)(x) & 0x000000ff) << 24) ;
}
/*
|表示按位或運算,比如: 0x0F | 0xF0 = 0xFF
\ 表示連接下一行,一般用于定義宏的時候,因為宏定義只有一行,而有時需要寫成多行方便查看,比如:
#define exchange(a, b) { int c;
c = a;
a = b;
b = c;}
上面的定義等價于:
#define exchange(a, b) {int c; c = a; a = b; b = c;} */
int main(int argc,char *argv[])
{
printf("------------帶參宏-------------\n");
printf("%#x\n",BSWAP_16(0x1234)) ;
printf("%#x\n",BSWAP_32(0x12345678));
printf("------------函數調用-----------\n");
printf("%#x\n",bswap_16(0x1234)) ;
printf("%#x\n",bswap_32(0x12345678));
}
輸出結果:
------------帶參宏-------------
0x3412
0x78563412
------------函數調用-----------
0x3412
0x78563412
/* %#x是帶格式輸出, 效果為在輸出前加0x. */
這里有個思考?上面的哪個是轉換為大端,哪個是轉為小端了呢?
參看:STM32開發 – 進制與字符串間的轉換
舉個例子,比如數字 0x12 34 56 78在內存中的表示形式為:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
則:
轉換為大端:
pPack[2] = (u8)((len >> 8) & 0xFF);
pPack[3] = (u8)(len & 0xFF);
轉為為小端:
pPack[2] = (u8)(len & 0xFF);
pPack[3] = (u8)((len >> 8) & 0xFF);
第二種方法:
從軟件的角度理解端模式
使用 htonl, htons, ntohl, ntohs 等函數
參看:百度百科–htonl ()函數
參看:百度百科–htons ()函數
查看:man htonl
NAME
htonl, htons, ntohl, ntohs - convert values between host and network byte order
SYNOPSIS
#include <arpa/inet.h>
DESCRIPTION
The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
On the i386 the host byte order is Least Significant Byte first, whereas the network byte order, as used on the Internet, is Most
Significant Byte first.
翻譯:
htonl() //32位無符號整型的主機字節順序到網絡字節順序的轉換(小端->>大端)
htons() //16位無符號短整型的主機字節順序到網絡字節順序的轉換 (小端->>大端)
ntohl() //32位無符號整型的網絡字節順序到主機字節順序的轉換 (大端->>小端)
ntohs() //16位無符號短整型的網絡字節順序到主機字節順序的轉換 (大端->>小端)
注,主機字節順序,X86一般多為小端(little-endian),網絡字節順序,即大端(big-endian);
舉兩個小例子:
//示例一
#include <stdio.h>
#icnlude <arpa/inet.h>
int main (void)
{
union
{
short i;
char a[2];
}u;
u.a[0] = 0x11;
u.a[1] = 0x22;
printf (“0x%x\n”, u.i); //0x2211 為小端 0x1122 為大端
printf (“0x%.x\n”, htons (u.i)); //大小端轉換
return 0;
}
輸出結果:
0x2211
0x1122
//示例二
#include <stdio.h>
#include <arpa/inet.h>
struct ST{
short val1;
short val2;
};
union U{
int val;
struct ST st;
};
int main(void)
{
int a = 0;
union U u1, u2;
}
輸出結果:
u1.val is 0x12345678
val1 is 0x5678
val2 is 0x1234
after first convert is: 0x78563412
after second convert is: 0x78563412
在對普通文件進行處理也需要考慮端模式問題。在大端模式的處理器下對文件的32,16位讀寫操作所得到的結果與小端模式的處理器不同。單純從軟件的角度理解上遠遠不能真正理解大小端模式的區別。事實上,真正的理解大小端模式的區別,必須要從系統的角度,從指令集,寄存器和數據總線上深入理解,大小端模式的區別。
以下內容了解:
1、從系統的角度理解端模式
先補充兩個關鍵詞,MSB和LSB:
MSB:MoST Significant Bit ------- 最高有效位
LSB:Least Significant Bit ------- 最低有效位
2、實際中的例子
雖然很多時候,字節序的工作已由編譯器完成了,但是在一些小的細節上,仍然需要去仔細揣摩考慮,尤其是在以太網通訊、MODBUS通訊、軟件移植性方面。這里,舉一個MODBUS通訊的例子。在MODBUS中,數據需要組織成數據報文,該報文中的數據都是大端模式,即低地址存高位,高地址存低位。假設有一16位緩沖區m_RegMW[256],因為是在x86平臺上,所以內存中的數據為小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……為了方便討論,假設m_RegMW[0] = 0x3456; 在內存中為0x56、0x34。
現要將該數據發出,如果不進行數據轉換直接發送,此時發送的數據為0x56,0x34。而Modbus是大端的,會將該數據解釋為0x5634而非原數據0x3456,此時就會發生災難性的錯誤。所以,在此之前,需要將小端數據轉換成大端的,即進行高字節和低字節的交換,此時可以調用步驟五中的函數BigtoLittle16(m_RegMW[0]),之后再進行發送才可以得到正確的數據。
原文:https://blog.csdn.net/qq_29350001/article/details/54428265
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
- 上一篇: LInux--进程间通信
- 下一篇: 电容的选取