字节序、位序
字節(jié)序
??? 字節(jié)序,又稱端序、尾序,英文單詞為Endian,該單詞來(lái)源于于喬納森·斯威夫特的小說(shuō)《格列佛游記》,小說(shuō)中的小人國(guó)因?yàn)槌噪u蛋的問(wèn)題而內(nèi)戰(zhàn),戰(zhàn)爭(zhēng)開始是由于以下的原因:我們大家都認(rèn)為,吃雞蛋前,原始的方法是打破雞蛋較大的一端??墒钱?dāng)今皇帝的祖父小時(shí)候吃雞蛋,一次按古法打雞蛋時(shí)碰巧將一個(gè)手指弄破了,因此他的父親,當(dāng)時(shí)的皇帝,就下了一道敕令,命令全體臣民吃雞蛋時(shí)打破雞蛋較小的一端,違令者重罰。老百姓們對(duì)這項(xiàng)命令極為反感。歷史告訴我們,由此曾發(fā)生過(guò)六次叛亂,其中一個(gè)皇帝送了命,另一個(gè)丟了王位…關(guān)于這一爭(zhēng)端,曾出版過(guò)幾百本大部著作,不過(guò)大端派的書一直是受禁的,法律也規(guī)定該派的任何人不得做官。?1980年,Danny Cohen在其著名的論文"On Holy Wars and a Plea for Peace"中,為平息一場(chǎng)關(guān)于字節(jié)該以什么樣的順序傳送的爭(zhēng)論,而引用了該詞。
?????????在計(jì)算機(jī)科學(xué)領(lǐng)域中,字節(jié)序是指存放多字節(jié)數(shù)據(jù)的字節(jié)(byte)的順序,典型的情況是整數(shù)在內(nèi)存中的存放方式和網(wǎng)絡(luò)傳輸?shù)膫鬏旐樞?。有時(shí)候也可以用指位序(bit)。為了更好地理解,先看下面這段小程序,這個(gè)程序是把一個(gè)包含4位數(shù)字的字符串轉(zhuǎn)換為16進(jìn)制整數(shù)來(lái)存儲(chǔ),16進(jìn)制整數(shù)的每一個(gè)字節(jié)存儲(chǔ)一位數(shù)字字符。比如:”1234”,轉(zhuǎn)換成16進(jìn)制整數(shù)0x01020304。
程序1清單:
#include?<stdio.h>
#include?<conio.h>
?
int?main( )
{
?????char?input[4] = {0};
?????int?integer???= 0;
?????int?i;
?????printf("/r/n請(qǐng)輸入一個(gè)位數(shù),每一位的范圍是從到0到9/r/n");
?????for(i = 0; i < 4; i++)
?????{
?????????input[i] = getch();
?????????if(input[i] >?'9'?|| input[i] <?'0')
?????????{
??????????????printf("input error!/r/n");
??????????????return?1;
?????????}
?????????putch(input[i]);
?????}
????getch();
?????putch('/n');
?
?????for(i = 0; i < 4; i++)
?????{
?????????input[i] = input[i] -?'0';
?????}
?
?????memcpy((void*)&integer, (void*)input, 4);
?
?????printf("轉(zhuǎn)換后的進(jìn)制數(shù)是:0x%08x/r/n", integer);
?
?????getch();
?
?????return?0;
}
?
現(xiàn)在來(lái)分析一下這段代碼
首先,定義了一個(gè)字符數(shù)組input,用來(lái)接收用戶輸入的4個(gè)數(shù)字字符;
第二,把4個(gè)字符數(shù)字轉(zhuǎn)換成對(duì)應(yīng)的數(shù)字;
第三,把轉(zhuǎn)換的數(shù)字復(fù)制到整型變量integer中;
最后在屏幕上打印。
如果在PPC或者ARM的機(jī)器上編譯運(yùn)行這個(gè)程序,那么會(huì)在屏幕上打印出結(jié)果:0x01020304,這與我們的預(yù)期一致;但是在X86的機(jī)器上則打印出的結(jié)果是:0x04030201。這個(gè)令人驚訝的結(jié)果正是字節(jié)序問(wèn)題引起。下面來(lái)詳細(xì)談?wù)勥@個(gè)問(wèn)題。
?
????從計(jì)算機(jī)誕生之后,就有幾種不同的字節(jié)序,典型的是大端序(big endian)和小端序(little endian),當(dāng)然還有不常見的混合序(middle endian)。這些用來(lái)都是描述多字節(jié)數(shù)據(jù)在內(nèi)存中的存放方式的。以上面的16進(jìn)制數(shù)0x01020304為例,在計(jì)算機(jī)中需要用4個(gè)字節(jié)來(lái)保存它,’01’, ’02’,’03’,’04’各占一個(gè)字節(jié)。按照人類的計(jì)數(shù)習(xí)慣,最左邊的’01’,稱之為最高有效位(MSB,Most Significant Byte,它具有最高權(quán)重);最右邊的’04’稱之為最低有效位(LSB, Least Significant Byte,他具有最低權(quán)重);在計(jì)算機(jī)中需要用4個(gè)字節(jié)來(lái)保存它,其中’01’, ’02’,’03’,’04’各占一個(gè)字節(jié)。大端序的計(jì)算機(jī)保存這個(gè)數(shù)值時(shí),按照從低地址到高地址的順序分別保存MSB到LSB四個(gè)字節(jié),0x01020304的存儲(chǔ)情況如下圖所示:
?
而小端序的計(jì)算機(jī)則以相反的順序來(lái)保存它,如下圖所示:
?
?
可以看到,在小端序的計(jì)算機(jī)中,0x01020304的保存順序恰好與上面的程序1中相反,這就是最后輸出結(jié)果為0x04030201的原因;大端序的計(jì)算機(jī)中兩者保存順序一致,所以打印正確。
?
??? 我們可以在VC集成環(huán)境中來(lái)驗(yàn)證上面的分析。在VS2005中調(diào)試下面的小程序,在return語(yǔ)句處設(shè)置斷點(diǎn),斷住后打開內(nèi)存窗口查看&i處的內(nèi)容??梢灾庇^的看到在x86(小端序)的機(jī)器上整數(shù)的存放方式。
程序2清單:
#include?<stdio.h>
int?main()
{
?????int?i = 0x01020304;
?????printf("i = %#x/r/n",i);
?????return?0;
}
?
?
?
?
?
再看看如何才能讓程序1在大端序和小端序的機(jī)器上都能正確執(zhí)行呢?一個(gè)辦法就是利用預(yù)編譯宏,針對(duì)不同的機(jī)器定義定義不同的數(shù)據(jù)結(jié)構(gòu)。下面是一個(gè)例子:
程序3清單:
#include?<stdio.h>
#include?<conio.h>
?
typedef?union
{
?????struct{
#ifdef?BIG_ENDIAN
?????char msb;
?????char midb1;
?????char midb2;
?????char lsb;
#else
?????char?lsb;
?????char?midb2;
?????char?midb1;
?????char?msb;
#endif
?????} bytes;
?????int??var;
} INTEGER;
?
int?main()
{
?????int??i??????????= 0;
?????char?input[5]???= {0};
?????INTEGER integer = {0};
?
?????printf("/r/n請(qǐng)輸入一個(gè)位數(shù),每一位的范圍是從到到/r/n");
?????for(i = 0; i < 4; i++)
?????{
?????????input[i] = getch();
?????????if(input[i] >?'9'?|| input[i] <?'0')
?????????{
??????????????printf("input error!/r/n");
??????????????return?1;
?????????}
?????????putch(input[i]);
?????}
????getch();
?????putch('/n');
?
?????integer.bytes.msb???= input[0] -?'0';
?????integer.bytes.midb1 = input[1] -?'0';
?????integer.bytes.midb2 = input[2] -?'0';
?????integer.bytes.lsb???= input[3] -?'0';
?
?????printf("轉(zhuǎn)換后的進(jìn)制數(shù)是:0x%08x/r/n", integer.var);
?
?????getch();
?
?????return?0;
}
可以看到,這段代碼定義了兩套數(shù)據(jù)結(jié)構(gòu),通過(guò)BIG_ENDIAN這個(gè)宏定義來(lái)決定使用哪一套數(shù)據(jù)結(jié)構(gòu)。這是個(gè)笨拙卻有效的方法。下面這個(gè)例子則漂亮一些:
程序4清單
#include?<stdio.h>
#include?<conio.h>
#include?<memory.h>
#include?<winsock2.h>
?
int?main( )
{
?????char?input[4] = {0};
?????int?integer???= 0;
?????int?i;
?????printf("/r/n請(qǐng)輸入一個(gè)位數(shù),每一位的范圍是從到到/r/n");
?????for(i = 0; i < 4; i++)
?????{
?????????input[i] = getch();
?????????if(input[i] >?'9'?|| input[i] <?'0')
?????????{
??????????????printf("input error!/r/n");
??????????????return?1;
?????????}
?????????putch(input[i]);
?????}
????getch();
?????putch('/n');
?
?????for(i = 0; i < 4; i++)
?????{
?????????input[i] = input[i] -?'0';
?????}
?
?????memcpy((void*)&integer, (void*)input, 4);
?
?????integer = ntohl(integer);
?
?????printf("轉(zhuǎn)換后的進(jìn)制數(shù)是:0x%08x/r/n", integer);
?
?????getch();
?
?????return?0;
}
這個(gè)程序利用了大端序與人類書寫習(xí)慣一致的特點(diǎn),通過(guò)ntohl函數(shù)將整數(shù)進(jìn)行轉(zhuǎn)換。這個(gè)函數(shù)的功能是將網(wǎng)絡(luò)序轉(zhuǎn)換成主機(jī)序,在大端機(jī)器上它什么也不做,在小端機(jī)器上它會(huì)將輸入?yún)?shù)的值轉(zhuǎn)換成小端序的值。在windows環(huán)境下鏈接時(shí)別忘了將Ws2_32.lib庫(kù)添加進(jìn)來(lái)。
?
主機(jī)序和網(wǎng)絡(luò)序
主機(jī)序就是指主機(jī)的端序。
網(wǎng)絡(luò)字節(jié)序(網(wǎng)絡(luò)序)指多字節(jié)數(shù)據(jù)在網(wǎng)絡(luò)傳輸中的順序,TCP協(xié)議規(guī)定網(wǎng)絡(luò)序是大端序,即高字節(jié)先發(fā)送。因此大端序的機(jī)器接受到的數(shù)據(jù)可直接使用,小端序機(jī)器則需要轉(zhuǎn)換后使用。BSD socket API中定義了一組轉(zhuǎn)換函數(shù),用于16和32bit整數(shù)在網(wǎng)絡(luò)序和本機(jī)字節(jié)序之間的轉(zhuǎn)換。htonl,htons用于本機(jī)序轉(zhuǎn)換到網(wǎng)絡(luò)序;ntohl,ntohs用于網(wǎng)絡(luò)序轉(zhuǎn)換到本機(jī)序。一般來(lái)說(shuō),為了保證程序的可移植性,編寫代碼時(shí),發(fā)送的數(shù)據(jù)需要使用htonl、htons轉(zhuǎn)換,接收到的數(shù)據(jù)要使用ntohl、ntohs轉(zhuǎn)換。
注意:不存在對(duì)單字節(jié)整數(shù)進(jìn)行轉(zhuǎn)換的函數(shù)”ntohc”和”htonc”!
位序
?????????位序,一般用于描述串行設(shè)備的傳輸順序。一般說(shuō)來(lái)大部分硬件都是采用小端序(先傳低位),因此,對(duì)于一個(gè)字節(jié)數(shù)據(jù),大部分機(jī)器上收發(fā)的順序都一樣,不會(huì)有問(wèn)題,這就是為什么沒(méi)有針對(duì)單字節(jié)數(shù)據(jù)的API接口”ntohc”和”htonc”。當(dāng)然,也有例外,比如-I2C協(xié)議就是采用了大端序。這些細(xì)節(jié)只有在網(wǎng)絡(luò)協(xié)議的數(shù)據(jù)鏈路層底端才會(huì)碰到,對(duì)一般的程序員來(lái)說(shuō)很少涉及。
?????????但是在C語(yǔ)言中存在一種特殊的數(shù)據(jù)結(jié)構(gòu):位域。它的存在,使得C程序員能方便地進(jìn)行位操作(比如在網(wǎng)絡(luò)協(xié)議中經(jīng)常出現(xiàn)1bit或者多bit的標(biāo)示位,它們不是一個(gè)完整的字節(jié))。但同時(shí)也引起一些難以察覺(jué)的問(wèn)題,這些問(wèn)題的根源仍然是前面提到的端序。
?
?????????與字節(jié)序一樣,一個(gè)字節(jié)中的8個(gè)bit順序在不同端序的機(jī)器上并不相同。大端機(jī)器上從低地址到高地址順尋分別是msb->lsb,如下圖:
?
?
小端序的機(jī)器上則正好相反?
現(xiàn)代計(jì)算機(jī)的最小存儲(chǔ)單位是BYTE,無(wú)法對(duì)bit尋址,因此我們無(wú)法直接觀察每個(gè)字節(jié)內(nèi)部bit的順序。但是我們?nèi)匀豢梢酝ㄟ^(guò)位域來(lái)間接觀察字節(jié)內(nèi)部bit順序,以印證上面的說(shuō)法。
在C語(yǔ)言中,位域與結(jié)構(gòu)體類似,其語(yǔ)法規(guī)定:先聲明的成員位于低地址,后聲明的成員位于高地址。那么下面的位域中:
typedef?struct?OneByte
{
?????bt0 : 1;
?????bt1 : 1;
?????bt2 : 1;
?????bt3 : 1;
?????bt4 : 1;
?????bt5 : 1;
?????bt6 : 1;
?????bt7 : 1;
}
成員bt0就位于一個(gè)字節(jié)中最低地址bit0處,成員bt7就位于一個(gè)字節(jié)的最地址bit7處。
?
我們看看下面的程序。
#include?<stdio.h>
?
typedef?struct?OneByte
{
?????char?bt0 : 1;
?????char?bt1 : 1;
?????char?bt2 : 1;
?????char?bt3 : 1;
?????char?bt4 : 1;
?????char?bt5 : 1;
?????char?bt6 : 1;
?????char?bt7 : 1;
} ONE_BYTE;
?
int?main()
{
?????ONE_BYTE onebyte = {0};
????
?????onebyte.bt7 = 1;
?
?????printf("onebyte = %#x/r/n", *((unsigned?char?*)&onebyte));
?
?????return?0;
}
?
當(dāng)bt7賦值為1后,onebyte在內(nèi)存中是這個(gè)樣子的:
?
而在VC2005中編譯運(yùn)行的結(jié)果如下:
?
0x80轉(zhuǎn)換成二進(jìn)制是1000 0000。由于在X86(小端序)中,高地址bit7是msb,因此onebyte的值是0x80了;這就證實(shí)了前面的說(shuō)法。?相應(yīng)的如果是在大端序計(jì)算機(jī)中,bit7是lsb,則onebyte的值是0x01。
?
?
?
未完待續(xù)
總結(jié)
- 上一篇: 科创板新股中签一手是多少股?科创板新股申
- 下一篇: 计算机启动软件,计算机软件及应用启动会-