字节的高低位
通常我们从最高有效位(most significant digit)开始自左向右书写一个数字。在理解有效位这个概念时,可以想象一下你的支票数额的第一位增加1和最后一位增加1之间的巨大区别,前者肯定会让你喜出望外。
计算机内存中一个字节的位相当于二进制数的位,这意味着最低有效位表示1,倒数第二个有效位表示2×1或2,倒数第三个有效位表示2×2×1或4,依此类推。如果用内存中的两个字节表示一个16位的数,那么其中的一个字节将存放最低的8位有效位,而另一个字节将存放最高的8位有效位,如下图。
存放最低的8位有效位的字节被称为最低有效位字节或低位字节,而存放最高的8位有效位的字节被称为最高有效位字节或高位字节。
-------- 高位字节 ------------- 低位字节--------
↓------------------------↓ ↓--------------------------↓
15 14 13 12 11 10 9. 8. 7. 6. 5. 4. 3. 2. 1. 0.
字节序与计算机存储大小端
由于现代设备的多样性,不同设备对于数据内容的存储格式有所差异,而目前对于数据的划分通常以字节为单位,由此引申出了字节序的概念,即:不同主机自有的主机字节序
和在主机与主机之间通道传播过程中的网络字节序
。
目前主机字节序
主要由主机字节内部的存储顺序决定为两种:
-
大端模式(big-endian):
是指数据的高字节
保存在内存的低地址
中,而数据的低字节
保存在内存的高地址
中 -
小端模式(little-endian):
是指数据的高字节
保存在内存的高地址
中,而数据的低字节
保存在内存的低地址
中
比如如果我们要将一个值为0x123456
的数据存储在计算机地址在0x4000 8000~0x4000 8002
范围内的内存中,根据不同的存储大小端可以得到以下结果:
计算机地址 | 0x4000 8000 | 0x4000 8001 | 0x4000 8002 |
---|---|---|---|
小端模式 | 0x56 | 0x34 | 0x12 |
大端模式 | 0x12 | 0x34 | 0x56 |
需要说明的是:如果此时有一个指针指向这一个数据,那么不论计算机存储模式是大端还是小端,这个指针的值始终为0x4000 8000
,即存储这个数据空间的最低位地址
目前在网络上传输的网络字节序
都通常默认为大端模式存储的字节顺序
大小端判断
对于计算机大小端的判断方法目前主流的有以下两种:
1. 通过指针类型的转换
由于如在32位操作系统中,char型变量在存储空间中占1字节,而int型变量在存储空间中占4字节,由此可以通过一个指向int型变量的指针强制转换成一个指向char型变量的指针,由于指针存储的仅是指向对象位置的地址,因此计算机在转换指针类型时并不会改变指针的值,而改变了在使用此指针间接读取指向对象的值时的读取方式,例如int型将读取4个字节,而char型仅读取一个字节。
通过这种操作便可以把int型变量在内存中首地址的第一个字节单元类容提取出来了。
代码如下:
//判断大小端,返回1为大端,返回-1为小端
int CheckEndian_1(){
int a = 0x123456;
int * pa = &a;
if(*((char *) pa) == 0x12) // big endian
return 1;
if(*((char *) pa) == 0x56) // little endian
return -1;
return 0;
}
2. 通过union的特性判断
在C语言中,不同于结构体,共用体(联合体,即union)中的几种不同类型的变量存放在同一段内存单元中,其数据成员都是从低地址开始存放的,通过对后续成员进行访问便能获取前一个成员的尾部字节的内容,如下:
//判断大小端,返回1为大端,返回-1为小端
int CheckEndian_2(){
union w{
int a; //4 bytes
char b; //1 byte
} c;
c.a = 0x123456;
if (c.b == 0x12) // little endian
return -1;
if (c.b == 0x56) // big endian
return 1;
return 0;
}
字节序的转换
主机在向网络发送/接收字节信息时,通常要将字节序进行一个转换,如函数ntohl(),其表示network to host (unsigned) long,即将long(32位)网络字节序转换成适应的主机字节序,其转换函数的代码也比较易于理解,如下:
#if _BYTE_ORDER == _BIG_ENDIAN // big endian
#define ntohl(x) (x)
#define ntohs(x) (x)
#define htonl(x) (x)
#define htons(x) (x)
#else // little endian
#define ntohl(x) ((((x) & 0x000000ff) << 24) | \
(((x) & 0x0000ff00) << 8) | \
(((x) & 0x00ff0000) >> 8) | \
(((x) & 0xff000000) >> 24))
#define htonl(x) ((((x) & 0x000000ff) << 24) | \
(((x) & 0x0000ff00) << 8) | \
(((x) & 0x00ff0000) >> 8) | \
(((x) & 0xff000000) >> 24))
#define ntohs(x) ((((x) & 0x00ff) << 8) | \
(((x) & 0xff00) >> 8))
#define htons(x) ((((x) & 0x00ff) << 8) | \
(((x) & 0xff00) >> 8))
#endif /* _BYTE_ORDER */
参考文章:
高位字节与低位字节
用union判断cpu的大小端
网络字节序和主机字节序详解
大小端
ntohl()、htonl()函数
使用Union判断大小端