使用id()结合ctypes修改整数内存时负数转换异常如何解决?
问题成因
- CPython存在小整数缓存优化机制:为了提升性能,CPython会在解释器启动时预创建[-5, 256]区间内的所有整数对象,作为全局共享的单例存在,整个解释器生命周期内所有用到该区间整数的地方,引用的都是同一个内存地址的对象。你直接通过ctypes修改该区间内整数的内存内容,会直接修改全局共用的实例,导致解释器内部所有依赖这些小整数的逻辑全部异常,最终触发解释器崩溃,你看到的退出码-1073740940就是Windows平台下解释器内部状态损坏触发的异常退出码。
- 修改后得到非预期值-252的原因:你修改了全局的4实例之后,print函数将整数转为字符串、计算输出缓冲区大小等逻辑内部也会用到4这个值,此时拿到的是你修改后的损坏对象,导致转码、计算逻辑全部出错,最终输出了错误的数值。你之前修改3为5能得到正确结果,只是因为修改后的数值暂时没有触发解释器内部逻辑报错,但本质上已经破坏了解释器的全局状态,依然存在崩溃风险。
修复方案
- 避开小整数缓存区间做测试
如果要做这类内存修改实验,使用257、-257这类超出[-5,256]区间的整数,这类整数不会被CPython全局缓存,每次创建都是独立的实例,修改单个实例不会影响解释器全局状态,也不会触发崩溃。示例修改后的测试代码:
- 避开小整数缓存区间做测试
x = 257 y = -257 print(x) mutate(x, y) print(x)
运行后会正常输出257和-257,不会出现异常。
- 优化mutate函数避免破坏对象元数据
当前的mutate函数会完整复制整个对象的内存,包括引用计数、类型指针这类元数据,很容易触发GC异常。你可以针对int对象只复制存储数值的部分,64位CPython下int对象的头部(引用计数+类型指针)共16字节,32位下为8字节,根据你的平台调整跳过头部即可:
- 优化mutate函数避免破坏对象元数据
def mutate_int(obj, new_obj): if not isinstance(obj, int) or not isinstance(new_obj, int): raise TypeError('only support int objects') # 64位CPython头部长度为16,32位为8,根据你的平台调整 header_size = 16 data_size = sys.getsizeof(obj) - header_size mem = (ctypes.c_byte * data_size).from_address(id(obj) + header_size) new_mem = (ctypes.c_byte * data_size).from_address(id(new_obj) + header_size) for i in range(data_size): mem[i] = new_mem[i]
- 非必要场景不要直接修改Python对象的内存
这类操作属于依赖CPython底层实现的hack,完全不符合Python的语法规范,不同版本、不同平台的CPython实现可能存在差异,生产环境绝对不要使用这类逻辑。
- 非必要场景不要直接修改Python对象的内存
内容的提问来源于stack exchange,提问作者mhay10
相关产品推荐
相关产品推荐

