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