字節序(Endianness):大端和小端

寫在前面:

本文章旨在總結備份、方便以後查詢,由於是個人總結,如有不對,歡迎指正;另外,內容大部分來自網絡、書籍、和各類手冊,如若侵權請告知,馬上刪帖致歉。

一、介紹

字節序,也就是字節的順序,指的是多字節的數據在內存中的存放順序;在內存中,數據是以字節(8bit)存儲的,當存儲 16bit或者 32bit時,就面臨着大端 (Big-Endian)存儲, 還是小端 (Little-Endian) 存儲的問題。


二、由來

先來講一個故事:從前,有一個國王的兒子,在喫雞蛋的時候,被蛋殼割破了手指;之所以發生這種情況是因爲這個國家有一個傳統,那就是在喫雞蛋前從雞蛋的較大一端開始打破。因爲這次事件,國王非常焦慮,決定禁止所有臣民在雞蛋較大的一端敲蛋;但是該政權的反對者躲了起來,他們想要保持傳統,繼續從較大的一邊打開雞蛋;爲了這一區區爭端,導致了小人國的內戰,甚至殃及鄰國,於是此舉也使得這兩個島嶼爆發了一場關於雞蛋的血腥戰爭… ——《格列佛遊記

在這裏插入圖片描述

當然了,實際上並不是這樣的,其實是因爲 IBM、Intel等大公司不願合作,自搞自的,而且當時也沒有發佈固定的標準,所以就一直沿用,才導致有大小端之分;說到底還是歷史遺留問題。

然後就牽涉出兩大 CPU派系:

  • Motorola 6800,PowerPC 970,SPARC(除V9外)等處理器採用 Big Endian方式存儲數據
  • x86系列,VAX,PDP-11等處理器採用 Little Endian方式存儲數據

因此,字節序只和處理器架構有關


三、大端序和小端序

雞蛋有兩個末端,一個較窄的末端(小端)和一個較寬的末端(大端)
在這裏插入圖片描述

1、大端(big-endian)

大端存儲:高字節存儲在低地址中,即高位先存

  • 數據以 8bit爲單位
地址增長方向
... 0x0A 0x0B 0x0C 0x0D ...

示例中,最高位字節是 0x0A 存儲在最低的內存地址處。下一個字節 0x0B存在後面的地址處。正類似於十六進制字節從左到右的閱讀順序。

  • 數據以 16bit爲單位
地址增長方向
... 0x0A0B 0x0C0D ...

最高的16bit單元 0x0A0B存儲在低位。

  • 32bit排列圖示

在這裏插入圖片描述

2、小端(little-endian)

小端存儲:低字節存儲在低地址中,即低位先存

  • 數據以 8bit爲單位
地址增長方向
... 0x0D 0x0C 0x0B 0x0A ...

最低位字節是 0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。

  • 數據以 16bit爲單位
地址增長方向
... 0x0C0D 0x0A0B ...

最低的16bit單元 0x0C0D存儲在低位。

  • 更改地址的增長方向

當更改地址的增長方向,使之由右至左時,表格更具有可閱讀性。

地址增長方向
... 0x0A 0x0B 0x0C 0x0D ...

最低有效位(LSB)是 0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。

地址增長方向
... 0x0A0B 0x0C0D ...

最低的16bit單元 0x0C0D存儲在低位。

  • 32bit排列圖示
    在這裏插入圖片描述

若是你還理解不了或者記不住他們所對應的關係,可以看下圖:
在這裏插入圖片描述


四、爲什麼要注意字節序

如果你寫的程序只在單機環境下面運行,而不需要與別人交互,那麼你完全可以忽略字節序的存在;若是存在交互,那麼就得注意了

eg:

1、在 ARM內核的 stm32f4xx中:
在這裏插入圖片描述

2、在 C8051內核的 51單片機裏:
在這裏插入圖片描述

根據上面所說的,因爲這兩款單片機的字節序(也就是他們存儲的方式)不一樣,若是實現通訊交互,那麼,他們彼此得到的數據將會出現顛倒現象;試想,如果 stm32f4xx單片機將變量 a = 0x12345678 的首地址傳遞給了 51單片機,由於 51單片機採取 Big Endian 方式存儲數據,若是不做處理,那麼很自然的它會將你的數據翻譯爲 0x78563412。顯然,問題就出現了!!!


五、字節序的判斷

/* 
	原理:
		聯合體 union的存放順序是所有成員都從低地址
		開始存放,而且所有成員共享存儲空間
*/
void Endianness(void)
{
	union temp
	{
		short int a;
		char b;
	}temp;
    
	temp.a = 0x1234;
	if( temp.b == 0x12 )//低字節存的是數據的高字節數據
	{
		printf("big-endian\n");//是大端模式
	}
	else
	{
		printf("little-endian\n");//是小端模式
	}
}

六、位序(一般用於描述串行設備的傳輸順序)

小端序(先傳低位)的串行協議

  • RS-232
  • RS-422
  • RS-485
  • USB
  • 以太網(雖然高字節先傳,但每一字節內低位先傳)

大端序(先傳高位)的串行協議

  • I2C協議
  • SPI協議
  • 摩爾斯電碼

七、大小端轉換

#define ReverseBytes_uint16(A)		((A & 0x00FFU) << 8 | (A & 0xFF00U) >> 8)

#define ReverseBytes_uint32(A)		(A & 0x000000FFU) << 24 | (A & 0x0000FF00U) << 8	\
									| (A & 0x00FF0000U) >> 8 | (A & 0xFF000000U) >> 24)

八、例子實驗

猜一下以下程序輸出什麼:

// 假設硬件平臺是intel x86(little endian)
typedef unsigned int uint32_t;
#define UC(b) (((int)b)&0xff) //byte轉換爲無符號int型

void inet_ntoa(uint32_t in){
    char  b[18];
    register  char  *p;
    p = (char *)&in;

    sprintf(b, "%d.%d.%d.%d\n", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
    printf(b);
}

int main(void){
    inet_ntoa(0x12345678);
    inet_ntoa(0x87654321);
    return 0;
}

先來看以下的函數:

int main(void){
    int a = 0x12345678;
    char *p = (char *)&a;
    char str[20];
    sprintf(str,"%d.%d.%d.%d\n", p[0], p[1], p[2], p[3]);
    printf(str);
    return 0;
}

按照小字節序的規則,變量a在計算機中存儲方式爲:

高地址方向 0x12 0x34 0x56 0x78 低地址方向
p[3] p[2] p[1] p[0]

  注意,p並不是指向 0x12345678的開頭 0x12,而是指向 0x78。p[0]到 p[1]的操作是 &p[0]+1,因此 p[1]地址比 p[0]地址大。輸出結果爲 120.86.52.18。
反過來的話,令 int a = 0x87654321,則輸出結果爲 33.67.101.-121。
爲什麼有負值呢?因爲系統默認的 char是有符號的,本來是 0x87也就是 135,大於 127因此就減去 256得到 -121。

那麼,最後結果爲:120.86.52.18和 33.67.101.135

九、結尾

參考:

https://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E5%BA%8F

https://www.errorediridondanzaciclico.com/Endianness.html

https://blog.erratasec.com/2016/11/how-to-teach-endian.html#.XnITCyIzapo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章