字節(jié)序和大小端
對(duì)于位數(shù)大于8位的處理器鸣奔,例如16位或者32位的處理器静稻,由于寄存器寬度大于一個(gè)字節(jié)域仇,那么必然存在著一個(gè)如何將多個(gè)字節(jié)安排的問(wèn)題放椰。
小端模式:(高高低低)高字節(jié)放在高地址作烟,低字節(jié)放在低地址
大端模式:(高低低高)高字節(jié)放在低地址,低字節(jié)放在高地址
舉個(gè)例子庄敛,數(shù)值 0x12345678俗壹,其中 0x12 這一端是高位字節(jié)科汗,0x78 這一端是低位字節(jié)藻烤。
該數(shù)值的存儲(chǔ)順序是這樣的:
為什么沒(méi)有統(tǒng)一?
檢查奇偶性 (小端占優(yōu))
小端序優(yōu)勢(shì)最明顯 的头滔,大概就是檢查奇偶性怖亭,即通過(guò)查看個(gè)位數(shù),確定某個(gè)數(shù)字是奇數(shù)還是偶數(shù)坤检。
以123456為例兴猩,大端序從左到右排列,計(jì)算機(jī)必須一直讀到最后一位的個(gè)位數(shù)6早歇,才能確定這是偶數(shù)倾芝。
小端序是從右到左排列,個(gè)位數(shù)在第一位箭跳。所以晨另,只要讀取第一位,就能確定它是偶數(shù)谱姓。
檢查正負(fù)號(hào) (大端占優(yōu))
一個(gè)類似的場(chǎng)景是檢查正負(fù)號(hào)借尿,確定一個(gè)數(shù)是正數(shù)還是負(fù)數(shù)。
大端序的符號(hào)位在左邊第一位屉来,小端序的符號(hào)位在右邊最后一位路翻。所以,大端序有優(yōu)勢(shì)茄靠,只看第一位就能知道是不是負(fù)數(shù)茂契。
比較大小 (小端占優(yōu))
下一個(gè)操作是比較大小。現(xiàn)在有三個(gè)數(shù)字慨绳,需要比較大械粢薄:43662576莫瞬,594,2郭蕉。
上圖是大端序排列疼邀,因?yàn)槭菑淖蟮接遗帕校匀齻€(gè)數(shù)字在右邊個(gè)位數(shù)對(duì)齊召锈。比較大小時(shí)旁振,計(jì)算機(jī)就不得不讀取每一個(gè)數(shù)的所有位,直到個(gè)位數(shù)涨岁,再進(jìn)行比較拐袜。
如果改成小端序,就是下面的排列方式梢薪。
小端序是從右到左蹬铺,所以三個(gè)數(shù)字在第一位對(duì)齊。計(jì)算機(jī)就不需要讀取所有位秉撇,哪個(gè)數(shù)字先讀不到下一位甜攀,就是最小的。比如琐馆,2這個(gè)數(shù)字就沒(méi)有第二位规阀,所以讀到第二位時(shí),就知道它是最小的瘦麸。
所以谁撼,比較大小時(shí),小端序有優(yōu)勢(shì)滋饲。
乘法 (小端占優(yōu))
接下來(lái)厉碟,再看乘法操作。
乘法是逐位相乘屠缭,每一輪乘法都要向前進(jìn)位箍鼓。
上圖是大端序的24165乘以3841。大端序的乘法是向左進(jìn)位勿她,也就是向左邊擴(kuò)展袄秩,必須等到每一輪的結(jié)果都出來(lái)(上例是四輪),再相加統(tǒng)一寫入內(nèi)存逢并。
如果改成小端序的乘法之剧,就不需要等待下一輪的結(jié)果,每一輪都可以直接寫入內(nèi)存砍聊。
上圖是小端序的24165乘以3841背稼。小端序的乘法是向右進(jìn)位,也就是向右邊擴(kuò)展玻蝌,左邊的邊界不變蟹肘。每一輪結(jié)果寫入內(nèi)存后词疼,就不需要移動(dòng),后面有變化只需要改動(dòng)對(duì)應(yīng)的位就行了帘腹。
因此贰盗,小端序的乘法有明顯優(yōu)勢(shì)。
任意精度整數(shù) (小端占優(yōu))
上一個(gè)例子的從低位開始計(jì)算的特性阳欲,對(duì)于任意精度整數(shù)特別有用舵盈。任意精度整數(shù)又稱大整數(shù),可以存放任意大小的整數(shù)球化。
它的內(nèi)部實(shí)現(xiàn)是把整數(shù)分成一個(gè)個(gè)較小的單位秽晚,通常是 uint32(無(wú)符號(hào)32位整數(shù))或 uint64(無(wú)符號(hào)64位整數(shù)),按順序組合在一起筒愚。
如果是大端序赴蝇,第一個(gè) u64 就是這個(gè)整數(shù)最大的部分。運(yùn)算時(shí)巢掺,一旦這個(gè)數(shù)發(fā)生變化句伶,需要進(jìn)位,后面的所有位都必須移動(dòng)和改寫址遇。小端序發(fā)生進(jìn)位時(shí)熄阻,往往就不需要所有位移動(dòng)。
小端序的另一個(gè)好處是倔约,如果逐字節(jié)的運(yùn)算從個(gè)位數(shù)開始(比如乘法和加法),可以從左到右依次運(yùn)算一個(gè)個(gè) u64坝初,算完上一個(gè)再讀取下一個(gè)浸剩。大端序就不行,必須讀取整個(gè)數(shù)以后再進(jìn)行運(yùn)算鳄袍。
更改類型 (小端占優(yōu))
最后一個(gè)例子是绢要,C 語(yǔ)言有一種 cast 操作,可以強(qiáng)制改變變量的數(shù)據(jù)類型拗小,比如把32位整數(shù)強(qiáng)行改變?yōu)?6位整數(shù)重罪。[圖片上傳中...(bg2022060114.jpg-7a8cc9-1701243293826-0)]
上圖中,32位整數(shù)0x00000001更改為16位整數(shù)0x0001哀九,大端序是截去前面兩個(gè)字節(jié)剿配,這時(shí)指向這個(gè)地址的指針必須向后移動(dòng)兩個(gè)字節(jié)。
小端序就沒(méi)有這個(gè)問(wèn)題阅束,截去的是后面兩個(gè)字節(jié)呼胚,第一位的地址是不變的,所以指針不需要移動(dòng)息裸。
網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序
網(wǎng)絡(luò)字節(jié)序:TCP/IP各層協(xié)議將字節(jié)序定義為Big Endian蝇更,即大端模式沪编,TCP/IP協(xié)議中使用的字節(jié)序是大端序,方便不同主機(jī)字節(jié)序的設(shè)備進(jìn)行網(wǎng)絡(luò)傳輸數(shù)據(jù)年扩。
主機(jī)字節(jié)序:整數(shù)在內(nèi)存中存儲(chǔ)的順序蚁廓,目前以Little Endian,即小端模式厨幻,比較普遍(不同的CPU有不同的字節(jié)序)纳令。iOS、macOS都是小端序克胳。
注意:不少文章說(shuō)macOS是大端序平绩,是錯(cuò)誤的(參考:將 macOS App 移植到 Apple 芯片 - Apple Developer )。
總結(jié):
如果需要逐位運(yùn)算漠另,或者需要到從個(gè)位數(shù)開始運(yùn)算捏雌,都是小端序占優(yōu)勢(shì)。反之笆搓,如果運(yùn)算只涉及到高位性湿,或者數(shù)據(jù)的可讀性比較重要,則是大端序占優(yōu)勢(shì)满败。
一些硬件廠商的堅(jiān)持肤频,因此在多字節(jié)存儲(chǔ)順序上始終沒(méi)有一個(gè)統(tǒng)一的標(biāo)準(zhǔn)。
如何判斷大小端算墨?
通過(guò)讀取低位地址
#include <stdio.h>
int main() {
__uint16_t val = 0x1234;
char a = ((char *) &val)[0]; // 低位地址
char b = ((char *) &val)[1]; // 高位地址
printf("a = %x\n", a);
printf("b = %x\n", b);
if (a == 0x34) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}
return 0;
}
利用聯(lián)合體
聯(lián)合體是一種特殊的數(shù)據(jù)結(jié)構(gòu)宵荒,聯(lián)合體中的成員變量共用同一段內(nèi)存。
我們定義一個(gè) test 聯(lián)合體净嘀,設(shè)置兩個(gè)成員變量 a 和 b报咳。
#include <stdio.h>
int main()
{
union test {
__uint32_t a;
char b;
};
union test val;
val.a = 0x12345678;
printf("%x\n", val.b);
if (val.b == 0x78) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}
return 0;
}
C 語(yǔ)言內(nèi)置宏
// 小端模式
# define LITTLE_ENDIAN __LITTLE_ENDIAN
// 大端模式
# define BIG_ENDIAN __BIG_ENDIAN
// 當(dāng)前主機(jī)的字節(jié)序
# define BYTE_ORDER __BYTE_ORDER
#include <endian.h>
int main()
{
if (BYTE_ORDER == LITTLE_ENDIAN) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}
return 0;
}
大小端轉(zhuǎn)換
手動(dòng)實(shí)現(xiàn)轉(zhuǎn)換邏輯
只需要將高位字節(jié)與低位字節(jié)進(jìn)行交換,就可以實(shí)現(xiàn)大小端的轉(zhuǎn)換挖藏。
int main()
{
__uint32_t val = 0x12345678;
unsigned char *x = (unsigned char *) &val, tmp;
// 0x78 與 0x12 進(jìn)行交換
tmp = x[0];
x[0] = x[3];
x[3] = tmp;
// 0x56 與 0x34 交換
tmp = x[1];
x[1] = x[2];
x[2] = tmp;
// 輸出:0x78563412
printf("0x%x\n", val);
return 0;
}
C 語(yǔ)言內(nèi)置宏
// 轉(zhuǎn)換 16 位整數(shù)
htobe16(x)
be16toh(x)
// 轉(zhuǎn)換 32 位整數(shù)
htobe32(x)
be32toh(x)
// 轉(zhuǎn)換 64 位整數(shù)
htobe64(x)
be64toh(x)
- h 的意思是 host暑刃,表示小端模式。
- be 的意思是 big-endian膜眠,表示大端模式岩臣。
- 16、32宵膨、64 的意思是 16 位架谎、32 位、64 位整數(shù)柄驻,表示不同位數(shù)的整數(shù)轉(zhuǎn)換狐树。
參考:
將 macOS App 移植到 Apple 芯片 - Apple Developer
字節(jié)序探析:大端與小端的比較 - 阮一峰的網(wǎng)絡(luò)日志
大端模式和小端模式 - 她和她的貓