理解大端序和小端序

开始之前

文章正式开始之前,先来做一个小测试

前提

已知运行平台(x86_64,Linux,gcc 9.3.0)中,一个int数据类型的大小是4Byte。
也就是说,一个int类型的数据会占用内存中一块连续4Byte大小的区域。
有这么一个int十进制整数:305419896,用十六进制表示的话就是0x12345678

使用4Byte(32bit) 的二进制表示的话,就是

[0] [1] [2] [3]
00010010 (0x12) 00110100 (0x34) 01010110 (0x56) 01111000 (0x78)

C语言的内存魔法

既然一个int是占了4个Byte,那么有没有办法把每个Byte的内容单独取出来而不是把4个Byte作为一个int整体呢?

当然可以

这时候就要用到C语言神奇的指针和强制类型转换的

// 平平无奇的十六进制写法的int整数
int a = 0x12345678;

// 获取变量a的指针,并把指针类型强制类型转换成unsigned char*型
unsigned char * p = (unsigned char *)&a;

for(int i = 0; i < sizeof(int); i++) {
    printf("%x ", p[i]); // 输出变量a内存地址起始位置开始,每Byte的十六进制内容
}

unsigned char * p = (unsigned char *)&a;这么做的讲究是什么?

目的是因为int指针的每次做加减法运算时,地址偏移的单位都是4,而char型指针地址每次偏移的量是1,这样就能实现对一个长度为4Byte的int内存区域按每1Byte进行操作了

输出内容是啥?
也许你以为是0x12 0x34 0x56 0x78吧。

实际上是:0x78 0x56 0x34 0x12

字节序

大小端序

震惊,居然是和我们人类直观的做法是倒着来的。

像上面这种对人类来说,本应该在内存最高位的数据,被放到内存最低位的字节顺序,就被称为小端序(Little-Endian)。

相反,和我们人类直观直觉相符合的,就是大端序了(Big-Endian)

为什么要有字节序

计算机内部基本的存储单位是Byte(字节),1Byte = 8bit,能存储的大小相当有限,数据之大,1Byte装不下的时候,就要用好几个Byte一起去存,比如1个int,就用到了4个Byte去存储。
既然你用了连续多个Byte存储,总得有个先后顺序对吧,所以这就产生了“字节序”的概念。

为什么我们的计算机采用了小端序

对于我们人类来说,进行加减乘除四则运算的时候,都是从个位数开始计算

比如12345 + 67,先算个位5+7=12,再算十位4+6+1(进位)=11,最后算百位3+1(进位)=4,最终结果12412

欸?发现了没?虽然我们数字书写是从左往右,但我们运算的时候,是从右往左,也就是先从低位开始计算。而正好我们的计算机电路在存取数据的时候,也是从地址的低位到高位的,于是乎科学家们干脆直接就让这种大于1Byte的数据采用小端序在计算机中进行存储和运算。

所有计算机/计算机的一切都是小端序么?

当然不是,PowerPC、IBM、Sun就是采用大端序存储的,而x86、DEC则采用了小端序。

大端序的好处:内存低位第一个就能读到符号位,判断正负很快啊!(马老师配音)
小端序的好处:前面说的运算高效方便,类型强转不需要重新调整(比如int转short,低位都不需要动的,高位不理就完事了)

而对于网络协议中使用的字节序,则统一规范使用了大端序,所以构造和解析网络协议报文的时候,就得根据主机自己的字节序来进行必要的转换了。

本机字节序的判断

使用C语言代码简单实现

#include <stdio.h>
#define BIGENDIAN 1
#define LITTLEENDIAN 0
int get_byte_seq() {
    int static flag = -1; // 结果缓存,多次调用就不需要做重复运算了
    if (flag == -1) {
        int a = 0x12345678;
        unsigned char * b = (unsigned char *)&a;
        if (b[0] == 0x78) { // 低位的内存地址是数据的低位值,就是小端序
            flag = LITTLEENDIAN;
        } else {
            flag = BIGENDIAN;
        }
    }
    return flag;
}

int main(int argc, char const *argv[])
{
    printf("字节序类型:%s\n", get_byte_seq() == BIGENDIAN ? "大端序" : "小端序");
    return 0;
}