读出来的血量是个天文数字?我被"类型"和"字节序"坑了三次
这篇用"排错日记"的写法。因为有段时间我读内存老出鬼数值——血量能读出 1095221716,坐标读出负数,金币读出一段乱码。每个 bug 背后都是同一个真相:内存读写不是"读个数",是"按正确的类型、字节序、长度,去解释一段原始字节"。 解释错了,再准的地址也白搭。
现象:血量 = 1095221716?
代码就这么一行:hp = pm.read_int(addr),结果常年返回 1095221716。我当时盯着这个数怀疑人生——谁家血量十亿起步啊。直到我把它的十六进制打出来:0x414570B4。把这串字节按单精度浮点解释,恰好是 12.34。破案了——游戏里血量其实是 float,我按 int32 读了,读到的根本是 float 的位模式被当成整数。
诊断一:类型选错,头号嫌疑
内存里一切皆字节,float、int、double 只是"怎么解释这串字节"的约定。用错类型,数值就天差地别。经验法则:见到异常大、带小数的预期整数、或剧烈跳变的值,先怀疑类型。游戏里血量/坐标/伤害大概率是 float,等级/数量才可能是 int。
诊断二:小端序,自己拼字节时的暗箭
x86/x64 都是小端序:低位字节放在低地址。比如一个 4 字节值,内存里是 [B0 B1 B2 B3],实际数值是 B3B2B1B0(反过来)。用 read_int/read_float 这类库函数会自动处理;但你要是自己 read_bytes 再手动拼,很容易忘记反转,结果差出十万八千里。
诊断三:长度不对,截掉一半
double 是 8 字节,int64 也是 8 字节,int32/float 是 4 字节。用 read_int(4 字节)去读一个 8 字节的 double,等于只读了一半,剩下一半是隔壁内存的,出来自然是垃圾。读之前务必确认目标类型占几个字节。
import struct, pymem
pm = pymem.Pymem("game.exe")
addr = ... # 已经正确定位到的血量地址
# ❌ 错误:把 float 的 4 字节当 int 读
wrong = pm.read_int(addr) # 1095221716(其实是 float 的位模式)
# ✅ 正确:按 float 读(小端)
raw = pm.read_bytes(addr, 4) # 取原始 4 字节
right = struct.unpack("<f", raw)[0] # "<f" = 小端 + 单精度浮点 → 12.34
# 不确定类型时,先把原始十六进制打出来对照
print(raw.hex()) # 414570b4 → 反查就是 float 12.34
- 打原始十六进制,对照猜它到底是 int 还是 float。
- float 误当 int,是头号嫌疑,优先排除。
- 数值特别大/特别小/负数,先确认读的长度(4 还是 8 字节)。
- 自己拼字节时,记住 x86 是小端,低地址是低位。
- 库函数(read_int/read_float/read_double)会帮你处理字节序,优先用。
内存读写的坑,八成不在"找地址",而在"解释字节"。地址对了、类型错了,等于你拿着正确的门牌敲开了错误的房间。把类型、字节序、长度这三点焊死在脑子里,那些天文数字、负数、乱码会一夜之间消失。这三次学费交得值——从那以后我再没被读出来的"10 亿血量"吓到过。
