一、大端、小端
"大端"和"小端"表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;
或者说:
1.小端法(Little-Endian)就是低位字节排放在内存的低地址端(即该值的起始地址),高位字节排放在内存的高地址端;
2.大端法(Big-Endian)就是高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端;
举个简单的例子,对于整型数据0x12345678,它在大端法和小端法的系统中,各自的存放方式如下图1所示:
二、网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
所以说,网络字节序是大端字节序;
比如,我们经过网络发送整型数值0x12345678时,在80X86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值;如下图2所示:
三、字节序测试
不同CPU平台上字节序通常也不一样,下面这个简单的代码可以测试不同平台上的字节序:
#include <stdio.h>
#include <netinet/in.h>
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
INTEL平台上的输出:
local order:
[0]: 0x78 addr:1245044 //低位字节存放在低地址处,则是小端法;
[1]: 0x56 addr:1245045
[2]: 0x34 addr:1245046
[3]: 0x12 addr:1245047 //高位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:1245044 //由此看出,主机字节序与网络字节不一样;
[1]: 0x34 addr:1245045
[2]: 0x56 addr:1245046
[3]: 0x78 addr:1245047
"大端"和"小端"表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;
或者说:
1.小端法(Little-Endian)就是低位字节排放在内存的低地址端(即该值的起始地址),高位字节排放在内存的高地址端;
2.大端法(Big-Endian)就是高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端;
举个简单的例子,对于整型数据0x12345678,它在大端法和小端法的系统中,各自的存放方式如下图1所示:
二、网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
所以说,网络字节序是大端字节序;
比如,我们经过网络发送整型数值0x12345678时,在80X86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值;如下图2所示:
三、字节序测试
不同CPU平台上字节序通常也不一样,下面这个简单的代码可以测试不同平台上的字节序:
#include <stdio.h>
#include <netinet/in.h>
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
INTEL平台上的输出:
local order:
[0]: 0x78 addr:1245044 //低位字节存放在低地址处,则是小端法;
[1]: 0x56 addr:1245045
[2]: 0x34 addr:1245046
[3]: 0x12 addr:1245047 //高位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:1245044 //由此看出,主机字节序与网络字节不一样;
[1]: 0x34 addr:1245045
[2]: 0x56 addr:1245046
[3]: 0x78 addr:1245047
至于说(UINT)(*p)为什么要移24位,其实是很好理解的,将0x00000012变成0x12000000,不就是向左移24位吗。
当然,向上面这样写时为了方便理解,可以更简单的写一个函数用于完成上面的转换功能,函数如下:
1
2
3
4
5
|
UINT
EndianConvertLToB(
UINT
InputNum) {
UCHAR
*p = (
UCHAR
*)&InputNum;
return
(((
UINT
)*p<<24)+((
UINT
)*(p+1)<<16)+
((
UINT
)*(p+2)<<8)+(
UINT
)*(p+3));
}
|
同样的原理适用于大端转小端,但是大端转小端时移位有差别,函数如下:
1
2
3
4
5
|
UINT
EndianConvertBToL(
UINT
InputNum) {
UCHAR
*p = (
UCHAR
*)&InputNum;
return
(((
UINT
)*p)+((
UINT
)*(p+1)<<8)+
((
UINT
)*(p+2)<<16)+(
UINT
)*(p+3)<<24);
}
|
下面验证下本机CPU属于哪种字节存储顺序。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <iostream>
using
namespace
std;
typedef
unsigned
int
UINT
;
typedef
unsigned
char
UCHAR
;
int
main()
{
UINT
i=0x12345678;
cout<<hex<<i<<endl;
UCHAR
*p = (
UCHAR
*)&i;
//将i的地址传给数组指针p,实际上p指向的地址是i在内存中存储的第一个字节,大端就是0x12,小端就是0x78
if
((*p==0x78)&(*(p+1)==0x56))
cout<<
"小端"
<<endl;
else
if
((*p==0x12)&(*(p+1)==0x34))
cout<<
"大端"
<<endl;
else
cout<<
"这是神马字节顺序呢?"
;
return
0;
}
|