Skip to main content

struct

计算机并不理解 Python 的对象,它只理解字节。当你需要和 C 语言编写的库通信、读取一份 90 年代的二进制游戏存档、或者通过 TCP 协议传输一个自定义的数据包时,你不能直接把一个 Python 的 dict 扔过去。

格式字符

struct 的设计初衷非常纯粹:它是 Python 对象与 C 结构体(C Structs)之间的桥梁。 它解决的核心问题是:

  • 内存对齐(Alignment): 处理不同 CPU 架构下字节排列的差异。
  • 紧凑性: 将 Python 庞大的对象压缩成机器能直接读取的二进制格式。
  • 字节序(Endianness): 解决大端(Big-endian)和小端(Little-endian)差异。

具体的实现方式是:用一个"格式字符串"来描述二进制数据的长相。

以下是C的常用数据类型和对应的Python类型:

字符C 类型Python 类型字节数
iint整数4
hshort整数2
Qunsigned long long整数8
ffloat浮点数4
schar[]字节串 (bytes)1s=1字节
?_Bool布尔值1
import struct

# 格式说明:
# i -> int (4字节)
# h -> short (2字节)
# h -> short (2字节)
# ? -> bool (1字节)
fmt = "ihh?" # 表示4个字节,2个字节,2个字节,1个字节

# 准备数据
id_val = 1024
x, y = 100, 200
status = True

# 开始封箱
binary_data = struct.pack(fmt, id_val, x, y, status)

print(f"原始数据:{id_val, x, y, status}")
print(f"二进制流:{binary_data}") # 输出类似 b'\x00\x04\x00\x00...'

print(f"Python对象占用字节:{(id_val, x, y, status).__sizeof__()}") # 56
print(f"struct占用字节:{struct.calcsize(fmt)}") # 4+2+2+1 = 9 字节

# 开始拆箱
unpacked_data = struct.unpack(fmt, binary_data)
print(f"还原后的元组:{unpacked_data}")
# 输出: (1024, 100, 200, True)

字节序

不同的机器存储字节的顺序不同。为了保证跨平台通用,我们通常在格式字符串的最前面加一个前缀:

  • <:小端序(Little-endian,Intel CPU 常用)

  • >!:大端序(Big-endian,网络传输、老式架构常用)

  • @=:默认字节序(与系统平台一致)

字节序可以和格式字符组合使用,例如:<i>h@?

tip

数字 256 对应的十六进制是:0x0100。需要2个字节来存储。

大端序和人类计数思维一致,就是将高位字节放在前面,先记录01,再记录00。读取时合并为01,00

小端序更有利于计算机处理加法运算,就是将低位字节放在前面,先记录00,再记录01。读取时反着拼接合并为01,00

如果256通过小端序存储为:00 01,传输后通过大端序解析,则会解析出意料之外的数字1

import sys

val = 256
# 将数字转换为字节数组
# 'little' 代表小端,'big' 代表大端
print(f"数字 {val} 的小端字节序列: {val.to_bytes(2, 'little').hex(' ')}")
# 数字 256 的小端字节序列: 00 01
print(f"数字 {val} 的大端字节序列: {val.to_bytes(2, 'big').hex(' ')}")
# 数字 256 的大端字节序列: 01 00
print(f"你的电脑目前使用的是: {sys.byteorder}")