1. 程式人生 > 其它 >C語言-- 大端小端詳解

C語言-- 大端小端詳解

技術標籤:網路java程式語言linux資料結構

一、什麼是大端和小端

所謂的大端模式,就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。

所謂的小端模式,就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。

簡單來說:大端——高尾端,小端——低尾端

舉個例子,比如數字 0x12 34 56 78在記憶體中的表示形式為:

1)大端模式:

低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:

低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12

可見,大端模式和字串的儲存模式類似。

3)下面是兩個具體例子:

16bit寬的數0x1234在Little-endian模式(以及Big-endian模式)CPU記憶體中的存放方式(假設從地址0x4000開始存放)為:

記憶體地址小端模式存放內容大端模式存放內容
0x40000x340x12
0x40010x120x34

32bit寬的數0x12345678在Little-endian模式以及Big-endian模式)CPU記憶體中的存放方式(假設從地址0x4000開始存放)為:

記憶體地址小端模式存放內容大端模式存放內容
0x40000x780x12
0x40010x560x34
0x40020x340x56
0x40030x120x78

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 的首地址位置開始。

聯合是一個在同一個儲存空間裡儲存不同型別資料的資料型別。這些儲存區的地址都是一樣的,聯合裡不同儲存區的記憶體是重疊的,修改了任何一個其他的會受影響。

當然你也可以這樣

#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) ;
}
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));
return 0 ;
}
輸出結果:
------------帶參巨集-------------
0x3412
0x78563412
------------函式呼叫-----------
0x3412
0x78563412

這裡有個思考?上面的哪個是轉換為大端,哪個是轉為小端了呢?

舉個例子,比如數字 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 等函式 這個可以參考我的網路程式設計部分的知識第一節 深入淺出TCPIP之理解TCP報文格式和互動流程

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;
a = 0x12345678;
u1.val = a;
printf("u1.val is 0x%x\n", u1.val);
printf("val1 is 0x%x\n", u1.st.val1);
printf("val2 is 0x%x\n", u1.st.val2);
printf("after first convert is: 0x%x\n", htonl(u1.val));
u2.st.val2 = htons(u1.st.val1);
u2.st.val1 = htons(u1.st.val2);
printf("after second convert is: 0x%x\n", u2.val);
return 0;
}
輸出結果:
u1.val is 0x12345678
val1 is 0x5678
val2 is 0x1234
after first convert is: 0x78563412
after second convert is: 0x78563412

在對普通檔案進行處理也需要考慮端模式問題。在大端模式的處理器下對檔案的32,16位讀寫操作所得到的結果與小端模式的處理器不同。單純從軟體的角度理解上遠遠不能真正理解大小端模式的區別。事實上,真正的理解大小端模式的區別,必須要從系統的角度,從指令集,暫存器和資料匯流排上深入理解,大小端模式的區別。