工具拆解

大端小端:同一个数字的两种排法

0x12345678 在内存里到底是 12 34 56 78 还是 78 56 34 12?从名字的由来讲到实际踩坑,以及为什么浮点数同样受影响。

同一串字节 12 34 56 78,按大端读是 0x12345678(305419896), 按小端读是 0x78563412(2018915346)。

看到这两个数字,很多人第一反应是「我读错了」。其实没错,只是字节序不同。

名字的由来比技术更有意思

「大端」「小端」这两个词出自 Jonathan Swift 的《格列佛游记》。书里有个国家, 国民为了「鸡蛋该从大的一头敲还是小的一头敲」分成两派,打了多年内战。

1970 年代,Danny Cohen 在写网络协议的文章时借用了这个典故,把两种字节顺序 分别叫 Big-Endian 和 Little-Endian。这个命名流传至今——技术圈难得有这么带文学梗的术语。

区别到底在哪

一个 32 位整数由 4 个字节组成。问题不是「哪个字节在前」,而是「哪个字节放低地址」。

以 0x12345678 为例(12 是最高有效字节,78 是最低):

  • 大端:低地址放高字节 → 12 34 56 78
  • 小端:低地址放低字节 → 78 56 34 12

大端的排列方式和人写字(左边是高位)一致,看起来更「自然」; 小端的好处是,把 32 位值强转成 8 位时,低地址那个字节正好就是想要的值, 早期硬件做类型转换更方便。

两种都活着,因为历史

这不是技术优劣问题,是历史包袱。x86 从诞生起就是小端, ARM 一开始也是小端(后来支持双端模式), 而 IBM、摩托罗拉、Sun 的架构多是大端。

结果就是:网络协议为了中立,规定统一用大端,也就是「网络字节序」。 TCP/IP 头部里的端口号、IP 地址,全是大端。

顺便一个常被问到的点:RISC-V 规范规定数据内存访问为小端, 所以处理器和 x86 在这一点上是一致的,但和网络数据交接时仍然要转换。

实际踩坑的三种场景

1. 解析二进制文件

文件格式本身规定了字节序,而且不同格式可能不一样:

  • PNG、JPEG:大端
  • BMP、WAV、GIF:小端
  • ELF:跟随目标架构

这就是为什么 BMP 文件头里那个 42 4D("BM")之后跟着的文件大小, 在小端机器上读出来才是对的。

2. 读芯片寄存器

手册里给的通常是数值,比如「配置寄存器 = 0x0000A501」。但总线上是按字节传输的, 一次 8 位。这四个字节先发哪个?取决于芯片设计。

如果手册没写清楚,最简单的验证方法:写入一个已知值,再读回来, 看字节顺序对不对得上。

3. 跨架构传数据

x86 服务器写出的二进制文件,拿到大端设备上读,数值会完全错乱。 同理,网络抓包工具显示的字段顺序,和你按小端拼出来的结果,往往差一位。

浮点数也逃不掉

字节序影响的是「字节的排列」,而浮点数同样是多字节的,所以必然受影响。

同样是 12 34 56 78 四个字节:

  • 按大端的单精度浮点读:约 5.69 × 10⁻²⁸
  • 按小端的单精度浮点读:约 1.74 × 10³⁴

差了六十多个数量级,而且都是「合法的浮点数」,不会报错。这类 bug 最难查, 因为程序不崩溃,只是结果离谱。

怎么快速判断

几个实用技巧:

  • 看数值是否在合理范围。如果本该是几百的数算出了 10³⁸,八成是字节序反了
  • 看字节是否「镜像」。12 34 56 78 变成 78 56 34 12 是最典型的特征
  • 查协议规范。凡是涉及网络的,默认先按大端试
  • 拿一个已知值做对照。比如写入 1.0,看字节是不是 3F 80 00 00(大端)或 00 00 80 3F(小端)

工具里我做了「交换字节序」按钮,就是把整串字节倒过来。 排查时先点一下看看结果合不合理,比在脑子里换算快得多。

打开「字节序转换」→ 更多文章