大小端字节序与 magic number:union 实验与文件读取
在开发机上读取 MNIST idx 文件(大端格式)时涉及字节序与文件格式识别问题,记录如下。
定义
多字节数据在内存或文件中的字节排列顺序称为字节序,分两种:
- 大端(big-endian):高位字节在前
- 小端(little-endian):低位字节在前
以 0x12345678 为例,4 个字节从前到后的排列:
| 字节序 | 第1字节 | 第2字节 | 第3字节 | 第4字节 |
|---|---|---|---|---|
| 大端 | 12 |
34 |
56 |
78 |
| 小端 | 78 |
56 |
34 |
12 |
常见约定
- ARM 默认小端,x86 小端。开发机(Windows PC)为小端。
- Modbus、TCP/IP 协议为大端。
magic number
magic number 是文件开头固定的几个字节,用于标识文件格式。程序靠它识别文件类型,人靠扩展名。
| 文件 | 开头字节 | 含义 |
|---|---|---|
| PNG | 89 50 4E 47 |
“PNG” 的 ASCII |
| EXE | 4D 5A |
“MZ” |
| MNIST 图片 idx | 00 00 08 03 |
unsigned byte,3 维 |
| MNIST 标签 idx | 00 00 08 01 |
unsigned byte,1 维 |
程序先读 magic,验证格式后再继续读数据。
idx 的 magic 按大端读是一个整数,其低两个字节有编码含义:
1 | 0x00000803 |
图片 magic 对应整数 2051(0x0803),标签 magic 对应 2049(0x0801)。
小端机器读取大端文件
图片文件前 4 字节为 00 00 08 03(大端)。若小端机器直接 fread 到 int:fread 本身不做任何转换,只是把这 4 字节原样拷入内存;颠倒发生在 CPU 按小端解释这段内存时——低地址的 00 被当作最低字节,整数值变为 0x03080000 = 50,855,936,不是 2051。
需手动拼字节:
1 | unsigned char b[4]; |
b[0] 是大端中的最高字节,对应移位 << 24。代入 00 00 08 03:
1 | magic = (0x00<<24) | (0x00<<16) | (0x08<<8) | 0x03 |
此写法即手动实现大端读取。读取 idx 的 magic 和后续维度都应采用。
用 union 检测字节序
1 |
|
union 各成员共享同一块内存,写入与读取针对同一地址。
| 赋值 | 字节序 | age[0] | age[1] | 输出 |
|---|---|---|---|---|
li.name = 0x04D2 |
小端 | D2 |
04 |
d204 |
li.name = 0x04D2 |
大端 | 04 |
D2 |
04d2 |
li.name = 0x1234 |
小端 | 34 |
12 |
3412 |
li.name = 0x1234 |
大端 | 12 |
34 |
1234 |
输出结果即机器字节序。开发机输出 d204,为小端。
注意:%x 配 char 的符号扩展
上例低字节 0xD2 ≥ 0x80,在 signed char 下为负数。传给 printf 时整型提升为 int,负数符号扩展成 0xFFFFFFD2,%x 输出一长串而非 d2。
应显式转为 unsigned char,并用 %02x 保证补零:
1 | printf("%02x %02x", (unsigned char)li.age[0], (unsigned char)li.age[1]); |
小结
大小端只决定多字节数的排列方式,无对错。小端机器读大端文件,需要手动拼字节或显式做字节交换。读取 idx 的 magic 和维度时应使用上述拼字节方式,并先校验 magic 是否合法。
- 标题: 大小端字节序与 magic number:union 实验与文件读取
- 作者: Guo Xiaosheng
- 创建于 : 2026-08-03 21:10:00
- 更新于 : 2026-08-04 00:20:28
- 链接: https://serendipityb612.com/2026/08/03/2026-08-03-endianness-and-union/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。