LV005-大小端模式
先来看一下什么是大端小端,由于我之前学过单片机,所以看到这个概念并不陌生。
一、什么是大小端模式?
大端模式( Big-endian )是指将 数据的低位放在内存的高地址上,而 数据的高位放在内存的低地址上。
小端模式( Little-endian )是指将 数据的低位放在内存的低地址上,而数据的高位放在内存的高地址上。这种存储模式将地址的高低和数据的大小结合起来,高地址存放数值较大的部分,低地址存放数值较小的部分。
二、在内存中的不同
如数据 0x12345678 ,共 4 个字节数据,高位到低位分别为: 0x12 、 0x34 、 0x56 和 0x78共 4 个字节。假设这个数据存放在 64 位系统下 0x7fff1635a610 地址中,那么它所占用的连续四个字节的空间在大端模式下和小端模式下的存储情况如下图所示。

可以看到:
大端模式就是:高地址→ 低字节,低地址→ 高字节。计算机读取数据的方向,是从低地址开始读取的。
小端模式就是:高地址→ 高字节,低地址→ 低字节。计算机读取数据的方向,是从高地址开始读取的。
【记法】高存高,低存低,为小端;高存低,低存高,为大端。
Tips:也可以这样记,正常我们写地址都是从左到右或者从上到下地址递增,人眼看到的数据 0x12345678 从左到右,12 存在低地址的时候就是大端,刚好与我们看到的顺序相同。可以认为符合人眼看到的顺序的是大端,否则为小端。
md大端(符合人眼直观看到的) | 小端 地址: 0x10 0x11 0x12 0x13 | 0x10 0x11 0x12 0x13 数据: 12 34 56 78 | 78 56 34 12
三、为什么有大小端?
计算机中的数据是以字节( Byte )为单位存储的,每个地址单元都对应着一个字节,一个字节为 8bit 。
目前 CPU 的位数(就是一次能处理的数据的位数)都超过了 8 位(一个字节), PC 机、服务器的 CPU 基本都是 64 位的,嵌入式系统或单片机系统仍然在使用 32 位和 16 位的 CPU 。
在 C 语言中除了 8bit 的 char 之外,还有 16bit 的 short 型, 32bit 的 long 型(要看具体的编译器)。对于位数大于 8 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何存储多个字节的问题。因此就导致了大端存储模式和小端存储模式。
在网上看到有说计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的,所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序,所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。
常见大小端 CPU 如下:
大端 cpu:PowerPC
小端 cpu:x86、arm(内核支持大端但从未见过大端)
四、如何判断?
那我们的电脑或者是要使用的 CPU 是大端还是小端呢?我们又要如何判断呢?其实我们可以写个测试程序来判断大小端模式。
1. 通过指针判断
int 占 4 个字节,而 char 类型的指针是占一个字节的,如果我们把 int 强传为 char 类型的指针,只会保存一个字节的数据,那么我们只需要判断 char 里面的第一个字节和 int 里面的第一个字节是否是一致即可判断。如果一致则为小端模式,反之为大端模式。
这里以 C 语言为例,
#include <stdio.h>
int main(int argc, char *argv[])
{
int n = 0x1234;
int *p = &n;
char a = 0;
a = *((char*)p);
if (a == 0x34)
printf("little-endian \n");
else
printf("big-endian\n");
return 0;
}2. 通过共用体判断
共用体中的成员共用一片内存,所以我们可以定义一个 int 类型成员,再定义一个 char 类型成员,给 int 类型成员赋值,然后读 char 类型成员,这样就可以读低地址中的一个字节数据,若是对应 int 类型成员数据的高位,说明低地址存了高位,这就是大端模式,相反为小端模式。
#include <stdio.h>
union test
{
int a;
char b;
};
int main(int argc, char *argv[])
{
union test data;
data.a = 0x12345678;
if (data.b == 0x78)
printf("little-endian(%#x) \n", data.b);
else
printf("big-endian(%#x) \n", data.b);
return 0;
}3. linux 命令
若是安装了 lscpu 命令的话,可以直接看:
lscpu五、位序
1. 位序是怎样的
我们现在从位序的角度看 0x12345678中的0x12:
存放在大端CPU的RAM时,[0:7] = 0x12(0b00010010):
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
0 0 0 1 0 0 1 0
存放在小端CPU的RAM时,[7:0] = 0x12(0b00010010):
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0 0 0 1 0 0 1 0从位序的视角看一个多字节数0x12345678:
存放在大端CPU的RAM时,[0:31] = 0x12345678(0b0001 0010 0011 0100 0101 0110 0111 1000)
bit: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
val: 0 0 0 1 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 0 1 1 1 1 0 0 0
存放在小端CPU的RAM时,[31:0] = 0x12345678(0b0001 0010 0011 0100 0101 0110 0111 1000)
bit: 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
val: 0 0 0 1 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 0 1 1 1 1 0 0 02. 常见位序的错误理解
如果不知道位序这个概念,很可能会把多字节在RAM中存储的顺序弄错,如对多字节数0x12345678的错误理解:
在大端系统中,存储顺序错理解为:
bit: 07 06 05 04 03 02 01 00 15 14 13 12 11 10 09 08 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
val: 0 0 0 1 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 0 1 1 1 1 0 0 0
在小端系统中,存储顺序错理解为:
bit: 24 25 26 27 28 29 30 31 16 17 18 19 20 21 22 23 08 09 10 11 12 13 14 15 00 01 02 03 04 05 06 07
val: 0 0 0 1 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 0 1 1 1 1 0 0 0六、大小端转换
1. 库函数
在 C 语言库中,有完成大小端转换的函数,他们也被称为字节序转换函数,主要用于网络编程中,因为在网络传输中,一般要求是大端,而 inter 处理器是小端,network to host 理解为大端转小端,而 host to network 理解为小端转大端。
uint32_t htonl(uint32_t hostlong); /* 主机字节序---> 网络字节序 */
uint16_t htons(uint16_t hostshort); /* 主机字节序---> 网络字节序 */
uint32_t ntohl(uint32_t netlong); /* 网络字节序---> 主机字节序 */
uint16_t ntohs(uint16_t netshort); /* 网络字节序---> 主机字节序 */- h 为 host ,表示主机字节顺序;
- n 为 net ,表示网络字节顺序;
- l 表示无符号整型数据;
- s 表示无符号短整型数据。
2. 自己实现
思路就是把高位放到低位,低位放到高位,这里通过移位来实现:
#define Big2Little16(x) ((unsigned short)(x)&0xff00) >> 8 | \
((unsigned short)(x)&0x00ff) << 8\
#define Big2Little32(x) ((unsigned int)(x)&0xff000000) >> 24 | \
((unsigned int)(x)&0x00ff0000) >> 8 | \
((unsigned int)(x)&0x0000ff00) << 8 | \
((unsigned int)(x)&0x00ff) << 24参考资料: