字节序是由于不同的主处理器和操作系统,对大于一个字节的变量在内存中的存放顺序不同而产生的。
字节序通常有大端字节序(Big Endian)和小端字节序(Little Endian)两种分类方法。
"大端"和"小端"表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;
1.小端法(Little-Endian)就是低位字节排放在内存的低地址端(即该值的起始地址),高位字节排放在内存的高地址端;
2.大端法(Big-Endian)就是高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端;
例1:对于整型数据0x12345678,它在大端法和小端法的系统中,各自的存放方式如下图1所示:
例2:
大端序:
数据以8bit为单位:
示例中,最高有效位(MSB, Most Significant Byte)是0x0A存储在最低的内存地址处。下一个字节0x0B存在后面的地址处。正类似于十六进制字节从左到右的阅读顺序。
数据以8bit为单位:
地址增长方向 → | |||||
0x0D | 0x0C | 0x0B | 0x0A |
数据以16bit为单位:
地址增长方向 → | |||||
0x0C0D | 0x0A0B |
最低的16bit单元0x0C0D存储在低位。
网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理?
TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的。
所以说,网络字节序是大端字节序。
例:我们经过网络发送整型数值0x12345678时,在x86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值;如下图2所示:
伯克利bsd socket API定义了一组转换函数,用于16和32bit整数在网络序和本机字节序之间的转换:
htonl,htons用于本机序转换到网络序; ntohl,ntohs用于网络序转换到本机序。
#define ntohs(n) //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short
#define htons(n) //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short
#define ntohl(n) //网络字节顺序到主机字节顺序 n代表net, h代表host, l代表 long
#define htonl(n) //主机字节顺序到网络字节顺序 n代表net, h代表host, l代表 long
在使用 big endian 类型的系统中 这些函数会定义成空宏。
在网络程序开发时,或是跨平台开发时,应该注意保证只用一种字节序,不然两方的解释不一样就会产生 bug。
不同CPU平台上字节序通常也不一样,下面这个简单的代码可以测试不同平台上的字节序:
#include
#include // if windows
//#pragma comment(lib,"ws2_32.lib") if windows
int main(int argc,char** argv)
{
int num = 0x12345678;
unsigned char* pc = (unsigned char*)(&num);
printf("local order:\n");
printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);
printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);
printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);
printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);
num = htonl(num);
printf("htonl order:\n");
printf("[0]: 0x%X addr:%u\n", pc[0], &pc[0]);
printf("[1]: 0x%X addr:%u\n", pc[1], &pc[1]);
printf("[2]: 0x%X addr:%u\n", pc[2], &pc[2]);
printf("[3]: 0x%X addr:%u\n", pc[3], &pc[3]);
return 0;
}
SPARC平台上的输出:
local order:
[0]: 0x12 addr:4290770212 //高位字节存放在低地址处,则是大端法;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215 //低位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4290770212 //由此看出,主机字节序与网络字节一样;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215
X86平台上的输出:
local order:
[0]: 0x78 addr:4289157020 //低位字节存放在低地址处,则是小端法;
[1]: 0x56 addr:4289157021
[2]: 0x34 addr:4289157022
[3]: 0x12 addr:4289157023 //高位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4289157020 //由此看出,主机字节序与网络字节不一样;
[1]: 0x34 addr:4289157021
[2]: 0x56 addr:4289157022
[3]: 0x78 addr:4289157023