概述
字節(jié)序是指多字節(jié)數(shù)據(jù)在內(nèi)存中的存儲(chǔ)順序。主要有兩種字節(jié)序:大端序、小端序。
大端序:即Big-Endian,高位字節(jié)存放在低地址處,低位字節(jié)存放在高地址處。比如:16位整數(shù)0x1234,在大端序下會(huì)以0x12 0x34的形式存儲(chǔ)。
小端序:即Little-Endian,低位字節(jié)存放在低地址處,高位字節(jié)存放在高地址處。比如:16位整數(shù)0x1234,在小端序下會(huì)以0x34 0x12的形式存儲(chǔ)。
需要注意的是:不同計(jì)算機(jī)系統(tǒng)可能使用不同的字節(jié)序來(lái)存儲(chǔ)多字節(jié)的數(shù)據(jù)類型。當(dāng)這些系統(tǒng)之間進(jìn)行通信時(shí),如果不正確處理字節(jié)序問(wèn)題,就可能會(huì)導(dǎo)致數(shù)據(jù)解析錯(cuò)誤。比如:如果一臺(tái)機(jī)器是小端序,另一臺(tái)是大端序,它們之間的數(shù)據(jù)交換如果沒(méi)有經(jīng)過(guò)適當(dāng)?shù)霓D(zhuǎn)換,接收方可能會(huì)誤解發(fā)送方的數(shù)據(jù)。
檢測(cè)字節(jié)序
在C++中,檢測(cè)字節(jié)序有幾種不同的方法。下面,分別進(jìn)行介紹。
1、使用類型轉(zhuǎn)換。下面的代碼封裝了一個(gè)IsLittleEndian的函數(shù),用于判斷是否為小端序。在該函數(shù)中,首先初始化一個(gè)32位無(wú)符號(hào)整數(shù)uiValue,其值為0x01020304。然后,使用reinterpret_cast將uiValue的地址轉(zhuǎn)換為char*類型的指針,以便逐字節(jié)訪問(wèn)。檢查uiValue的第一個(gè)字節(jié)是否為0x04,如果是,則系統(tǒng)是小端序;否則,系統(tǒng)是大端序。
#include <iostream>
using namespace std;
bool IsLittleEndian()
{
unsigned int uiValue = 0x01020304;
char* pValue = reinterpret_cast<char*>(&uiValue);
// 如果第一個(gè)字節(jié)是0x04,則是小端序
return (pValue[0] == 0x04);
}
int main()
{
if (IsLittleEndian())
{
cout << "Little-Endian" << endl;
}
else
{
cout << "Big-Endian" << endl;
}
return 0;
}
2、使用聯(lián)合體。聯(lián)合體允許我們?cè)谙嗤膬?nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類型,這樣可以方便地檢查多字節(jié)數(shù)據(jù)類型的字節(jié)順序。在下面的代碼中,我們首先使用聯(lián)合體將一個(gè)32位無(wú)符號(hào)整數(shù)unsigned int i和一個(gè)4個(gè)字符的數(shù)組char c[4]共享同一塊內(nèi)存。然后,初始化聯(lián)合體中的unsigned int i為0x01020304。最后,通過(guò)char c[4]訪問(wèn)unsigned int i的第一個(gè)字節(jié)。檢查第一個(gè)字節(jié)是否為0x04,如果是,則系統(tǒng)是小端序;否則,系統(tǒng)是大端序。
#include <iostream>
using namespace std;
bool IsLittleEndian()
{
union {
unsigned int i;
char c[4];
} value = {0x01020304};
// 如果第一個(gè)字節(jié)是0x04,則是小端序
return (value.c[0] == 0x04);
}
int main()
{
if (IsLittleEndian())
{
cout << "Little-Endian" << endl;
}
else
{
cout << "Big-Endian" << endl;
}
return 0;
}
3、使用標(biāo)準(zhǔn)庫(kù)函數(shù)。在某些情況下,可以使用標(biāo)準(zhǔn)庫(kù)提供的函數(shù)來(lái)檢測(cè)字節(jié)序。比如:在POSIX兼容的系統(tǒng)中,可以使用endian.h頭文件中的宏來(lái)檢測(cè)字節(jié)序。具體如何使用,可參考下面的示例代碼。
#include <iostream>
#include <endian.h>
using namespace std;
int main()
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
cout << "Little-Endian" << endl;
#elif __BYTE_ORDER == __BIG_ENDIAN
cout << "Big-Endian" << endl;
#else
cout << "Unknown endianness" << endl;
#endif
return 0;
}
主機(jī)字節(jié)序與網(wǎng)絡(luò)字節(jié)序
主機(jī)字節(jié)序是指當(dāng)前計(jì)算機(jī)系統(tǒng)中,多字節(jié)數(shù)據(jù)類型的存儲(chǔ)順序。主要有上面介紹的兩種字節(jié)序:大端序和小端序。
在網(wǎng)絡(luò)協(xié)議中,為了確保數(shù)據(jù)的一致性,通常規(guī)定使用一種統(tǒng)一的字節(jié)序。最常見(jiàn)的網(wǎng)絡(luò)字節(jié)序是大端序,這是因?yàn)椋捍蠖诵蚺c人類閱讀數(shù)字的習(xí)慣一致,便于調(diào)試和理解。比如:在TCP/IP協(xié)議族中,所有多字節(jié)的數(shù)據(jù)類型都要求使用大端序進(jìn)行傳輸。
字節(jié)序轉(zhuǎn)換
在跨平臺(tái)或異構(gòu)網(wǎng)絡(luò)環(huán)境中,不同的系統(tǒng)可能使用不同的字節(jié)序。如果發(fā)送方和接收方的字節(jié)序不一致且沒(méi)有進(jìn)行適當(dāng)?shù)霓D(zhuǎn)換,會(huì)導(dǎo)致數(shù)據(jù)解析錯(cuò)誤。因此,在網(wǎng)絡(luò)編程中,必須確保數(shù)據(jù)在發(fā)送前轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,在接收后轉(zhuǎn)換回主機(jī)字節(jié)序。
C++標(biāo)準(zhǔn)庫(kù)提供了幾個(gè)用于字節(jié)序轉(zhuǎn)換的函數(shù)。對(duì)于POSIX兼容系統(tǒng)(比如:Linux、Mac等),這些函數(shù)定義在arpa/inet.h頭文件中。對(duì)于Windows系統(tǒng),這些函數(shù)定義在winsock2.h頭文件中。
htons()和htonl():用于將主機(jī)字節(jié)序轉(zhuǎn)網(wǎng)絡(luò)字節(jié)序(Host to Network Short/Long)。
ntohs()和ntohl():用于將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)主機(jī)字節(jié)序(Network to Host Short/Long)。
在下面的示例代碼中,我們使用htons和htonl將主機(jī)字節(jié)序的hostShort、hostLong轉(zhuǎn)換成了網(wǎng)絡(luò)字節(jié)序的netShort、netLong。然后,再將網(wǎng)絡(luò)字節(jié)序的netShort、netLong轉(zhuǎn)換回主機(jī)字節(jié)序的convertedShort、convertedLong。注意:在Windows下使用Visual Studio編譯這段程序時(shí),需要在工程中鏈接ws2_32.lib庫(kù),否則鏈接時(shí)會(huì)報(bào)錯(cuò)。
#include <iostream>
#include <winsock2.h>
using namespace std;
int main()
{
// 主機(jī)字節(jié)序轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
uint16_t hostShort = 0x1234;
uint32_t hostLong = 0x12345678;
uint16_t netShort = htons(hostShort);
uint32_t netLong = htonl(hostLong);
cout << "Host short: " << hex << hostShort << endl;
cout << "Network short: " << hex << netShort << endl;
cout << "Host long: " << hex << hostLong << endl;
cout << "Network long: " << hex << netLong << endl;
// 再將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)回主機(jī)字節(jié)序
uint16_t convertedShort = ntohs(netShort);
uint32_t convertedLong = ntohl(netLong);
cout << "Converted short: " << hex << convertedShort << endl;
cout << "Converted long: " << hex << convertedLong << endl;
return 0;
}