什么是字节序
字节序,简单来说,就是字节的顺序,指的超过一个字节的数据类型在内存中存储的顺序(一个字节显然不需要顺序)。
字节序在不同的主机平台上分为大端字节序、小端字节序。
小端字节序
高位字节数据存放在高地址处,低位数据存放在低地址处。linux和windows平台一般都采用小端字节序。
采用union可以打印出内存中的字节顺序:
void show_little_endian()
{
union{
char byte[4];
int num;
}test_num;
test_num.num = 0x12345678;
printf("[0]:%p:0x%X\n"
"[1]:%p:0x%X\n"
"[2]:%p:0x%X\n"
"[3]:%p:0x%X\n",
&test_num.byte[0], test_num.byte[0],
&test_num.byte[1], test_num.byte[1],
&test_num.byte[2], test_num.byte[2],
&test_num.byte[3], test_num.byte[3]);
/*
[0]:0x7ffdeb35bb94:0x78
[1]:0x7ffdeb35bb95:0x56
[2]:0x7ffdeb35bb96:0x34
[3]:0x7ffdeb35bb97:0x12*/
}
如上所示,低字节0x7ffdeb35bb94存放的是地位0x78,高字节0x7ffdeb35bb97存放的是高位0x12。
大端字节序
大端就是将高位字节放到内存的低地址端,低位字节放到高地址端。一般网络上都使用大端字节序进行二进制数据的传输。
void show_big_endian()
{
union{
char byte[4];
int num;
}test_num;
test_num.num = htonl(0x12345678);
printf("[0]:%p:0x%X\n"
"[1]:%p:0x%X\n"
"[2]:%p:0x%X\n"
"[3]:%p:0x%X\n",
&test_num.byte[0], test_num.byte[0],
&test_num.byte[1], test_num.byte[1],
&test_num.byte[2], test_num.byte[2],
&test_num.byte[3], test_num.byte[3]);
}
/*
[0]:0x7ffd7f6abbd4:0x12
[1]:0x7ffd7f6abbd5:0x34
[2]:0x7ffd7f6abbd6:0x56
[3]:0x7ffd7f6abbd7:0x78
*/
如上所示,低字节0x7ffd7f6abbd4存放的是低位0x12,高字节0x7ffd7f6abbd7存放的是高位0x78。
主机字节序
指运行程序的主机的字节序,可以采用下面方式获取主机的字节序:
bool isLittlEndian()
{
uint32_t n1 = 1;
uint32_t n2 = htonl(n1);
return n1 != n2; // 如果htonl生效(n2!=n1),则说明主机是小端,n2则是按大端字节序排序的
}
bool isLittlEndian2()
{
union{
char byte[4];
int num;
}test_num;
test_num.num = 0x01u;
return test_num.byte[0] == 0x01u; // 通过低字节是地位来判断
}
为什么要有大小端
小端字节序的优势:计算机电路先处理低位字节,采用小端字节序效率比较高。
大端字节序的优势:数据的储存方式和人的阅读方式一致,更利于直接阅读。
大小端的转换
将对应的字节顺序颠倒即可
#define BigLittleSwap16(A) ((((uint16_t)(A) & 0xff00) >> 8) | \
(((uint16_t)(A) & 0x00ff) << 8))
#define BigLittleSwap32(A) ((((uint32_t)(A) & 0xff000000) >> 24) | \
(((uint32_t)(A) & 0x00ff0000) >> 8) | \
(((uint32_t)(A) & 0x0000ff00) << 8) | \
(((uint32_t)(A) & 0x000000ff) << 24))
#define BigLittleSwap64(A) ((uint64_t)BigLittleSwap32((A & 0xFFFFFFFF)) << 32 | \
(uint64_t)BigLittleSwap32((A >> 32)))
double、float这类浮点数也需要进行字节序转换,转换方法参考同大小的int
常见类型的主机字节序和网络字节序转换
Linux 和 windows 都提供了接口对数据进行转换
uint32_t htonl(uint32_t hostlong); // 32位数据(int,int32_t,unsigned int,uint32_t,float)主机序转网络字节序
uint16_t htons(uint16_t hostshort); // 16位数据(short,int16_t,unsigned short,uint32_t,float)主机序转网络字节序
uint32_t ntohl(uint32_t netlong); // 32位数据(int,int32_t,unsigned int,uint32_t,float)网络字节序转主机序
uint16_t ntohs(uint16_t netshort); // 16位数据(int,int32_t,unsigned int,uint32_t,float)网络字节序转主机序
int64_t,uint64_t,double可以参考BigLittleSwap64
需要注意的是,windows和linux的long类型定义不同,windows定义的是32位,而linux定义的是64位,网络传输时会有歧义,应该避免使用
结构体的大小端转换
结构体内的每个元素都是独立的,需要对每个元素进行大小端转换。