大小端模式
我们从小就知道,数字是从左往右读的,这符合人类的普遍阅读习惯。
比如十进制数65535
,数位从高到低依次是万,千,百,十,个。对十进制数而言,最左边是高位数位,最右边是低位数位。
我们类比到 16 进制数0x12345678
,也从左往右来阅读这个数。那么,对十六进制数而言,最左边就是高位字节,最右边是低位字节。
端模式(Endian)的这个词出自 Jonathan Swift 书写的《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为 Big Endian,从尖头开始将鸡蛋敲开的人被归为 Littile Endian(这句话最为形象)。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业 Big Endian 和 Little Endian 也几乎引起一场战争。在计算机业界,Endian 表示数据在存储器中的存放顺序。下文举例说明在计算机中大小端模式的区别。
如果将一个 32 位的整数0x12345678
存放到一个整型变量(int) 中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。
大端 | 小端 |
---|---|
- 小端:较高的有效字节存放在较高的的存储器地址,较低的有效字节存放在较低的存储器地址。
- 大端:较高的有效字节存放在较低的存储器地址,较低的有效字节存放在较高的存储器地址
采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将高位存放在高地址。
到目前为止,采用大端或者小端进行数据存放,其孰优孰劣也没有定论:
- 小端模式:强制转换数据不需要调整字节内容,1、2、4 字节的存储方式一样。
- 大端模式:符号位的判定固定为第一个字节,容易判断正负。
# 为什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在 C 语言中除了 8bit 的 char 之外,还有 16bit 的 short 型,32bit 的 long 型(要看具体的编译器),另外,对于位数大于 8 位的处理器,例如 16 位或者 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x,在内存中的地址为 0x0010,x 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 x86 结构是小端模式,而 KEIL C51 则为大端模式。很多的 ARM,DSP 都为小端模式。有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。
# 判断大小端
# 使用联合体 union:
//return 1 : little-endian
// 0 : big-endian
int little_or_big()
{
union {
unsigned int a;
unsigned char b;
} c;
c.a = 1;
return c.b;
}
首先共用体元素 a 和 b 在访问时候都是从低地址开始访问的,c.a = 1 在内存中的存放有两种可能(内存地址从左到右递减),小端模式为 00 00 00 01
;大端模式为: 01 00 00 00
,而共用体 c 中的 b 是 char 类似,所以我们用 c.b 去访问时只能读取到最低地址的值(按 char 去解析时只会读取一个字节),所以,如果读出 c.b 的值为 1 则说明当前机器是小端模式,读出 c.b 的值为 0, 则说明当前机器是大端模式
# 指针方式来测试大小端
int little_or_big2(void)
{
int a = 1;
char b = *((char *) &a);
return b;
}
首选定义变量 a= 1,然后将 a 的指针强制类型转换成 char *
接着去解应用这个指针,并赋值给 b,然后根据 b 的返回值来确定大小端。其实分析可以发现其本质都是一样的,都是先给一个内存里面存一个 char 类型的 1,然后使得另一个 char 类型的变量 b 去读取这个内存的值,然后根据读取的值来判断大小端。