首先简要说明一下数据大小端模式。
大端模式
所谓的大端模式(Big-endian)指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式
所谓的小端模式(Little-endian),指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
内存中存储的区别:
以32bit的int型数据0x11223344为例,来说明大小端的区别:
大端 | 11 | 22 | 33 | 44 |
小端 | 44 | 33 | 22 | 11 |
内存地址 | 0x0000 | 0x0001 | 0x0002 | 0x0003 |
可见大小端只是针对16bit,32bit,64bit这些占用多于一个字节的数据类型,字节流不存在大小端问题。
大小端的使用情况
目前Intel的x86系列芯片是唯一还在坚持使用小端的芯片,ARM芯片默认采用小端,但可以切换为大端;而MIPS等芯片要么采用全部大端的方式储存,要么提供选项支持大端——可以在大小端之间切换。另外,对于大小端的处理也和编译器的实现有关,Java是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端模式。
可以看出,大小端只是在不同平台间进行数据交换是时候有用。如果在同一个平台下,不管数据在内存中实际是如何表示的,不会影响程序对数据的解释,也就是不用考虑大小端的问题。只有在网络传输时,因为会涉及到不同的平台,才需要考虑大小端的问题。一般情况下,网络上传输数据采用的都是大端模式,如不考虑大小端模式,有可能把一个整型的0x1234做为0x3412处理,导致程序异常。因此,在网络编程时就引入了主机字节序和网络字节序相互转换的函数。
系统字节序转换函数
系统标准定义的转换函数有4个:
htonl()--"Hostto Network Long int" 32bit
ntohl()--"Networkto Host Long int" 32 bit
htons()--"Hostto Network Short int" 16 bit
ntohs()--"Networkto Host Short int" 16 bit
在Linux系统下的头文件及函数定义:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
在Windows系统的头文件及函数定义:
#include<Winsock2.h>
u_short PASCAL FAR ntohs( u_short netshort);
u_long PASCAL FAR ntohl( u_long netlong);
u_short PASCAL FAR htons( u_short hostshort);
u_long PASCAL FAR htonl( u_long hostlong);
扩展的64bit long类型的大小端转换函数
static inline int isBigEndian(void) //测试主机大小端
{
union {
int32_t i;
char c[4];
}u;
u.i = 1;
return (u.c[0] == 0);
}
#define swab64(x) \
((uint64_t)(\
(((uint64_t)(x)& (uint64_t)0x00000000000000ffULL) << 56) | \
(((uint64_t)(x)& (uint64_t)0x000000000000ff00ULL) << 40) | \
(((uint64_t)(x)& (uint64_t)0x0000000000ff0000ULL) << 24) | \
(((uint64_t)(x)& (uint64_t)0x00000000ff000000ULL) << 8) | \
(((uint64_t)(x)& (uint64_t)0x000000ff00000000ULL) >> 8) | \
(((uint64_t)(x)& (uint64_t)0x0000ff0000000000ULL) >> 24) | \
(((uint64_t)(x)& (uint64_t)0x00ff000000000000ULL) >> 40) | \
(((uint64_t)(x)& (uint64_t)0xff00000000000000ULL) >> 56) ))
uint64_t htonll(uint64_t x)
{
if (isBigEndian()) {
return x;
}else {
return swab64(x);
}
}
uint64_t ntohll(uint64_t x)
{
return htonll(x);
}
对float类型的转换
系统函数和扩展的函数都是针对整型的数据类型。对于浮点数,如果也想使用这些函数,需要做些额外的工作。下面以64bit的double类型为例,来说明使用方法。
doubleinputData64;
假设inputData64是从网络端获取的double类型的数据,下面的代码把正确字节序的double类型的数据保存在data64中。
uint8_t * pData = (uint8_t *) & inputData64;
uint64_t tmp_64;
doubledata64;
tmp_64 = ntohll(*((uint64_t *)pData));
memcpy(&data64, &tmp_64, sizeof (uint64_t));