Python内存dump:基于id地址定位变量及区分栈堆边界
Python内存定位与栈堆边界解析
一、通过id()返回的地址定位变量
Python中id()返回的是对象在内存中的起始地址(CPython实现下),要在内存dump中定位到对应对象,可按以下步骤操作:
- 地址转换:把
id()输出的十进制地址转为十六进制(比如id(a)=140706166402816转成0x7FF9C555A000),这是对象结构体的起始位置。
- 地址转换:把
- 识别对象头部:CPython所有对象都以
PyObject结构体开头,64位系统下结构为:
在dump中定位到目标地址后,查看typedef struct _PyObject { _PyObject_HEAD_EXTRA // GC用双向链表指针,占16字节 Py_ssize_t ob_refcnt; // 引用计数,8字节 struct _typeobject *ob_type; // 类型指针,8字节 } PyObject;ob_type指针值,可对应到PyLong_Type(整数类型)的地址,以此验证是否为目标整数对象;后续字节则是对象的实际值(小整数直接存在结构体特定字段,大整数用数组存储)。- 识别对象头部:CPython所有对象都以
- 关联变量名:变量名(
a/b/c)并非直接存在堆对象中,而是存储在当前栈帧的局部变量表里。栈帧位于系统栈中,局部变量表是栈帧内的指针数组,每个元素指向对应对象的地址(即id()返回值)。你可以通过进程内存映射找到栈区域,再在栈帧结构中定位f_localsplus数组,匹配其中的指针与对象地址,结合栈帧中co_varnames元组(存储变量名)完成变量名与对象的关联。
- 关联变量名:变量名(
二、区分栈与堆的边界
在CPython中,栈和堆的分工与边界可通过以下方式识别:
- 栈区域:
- 存储内容:Python解释器的栈帧(含局部变量表、指令指针、返回地址)、C层函数调用的栈数据。
- 内存特征:在Linux
/proc/[pid]/maps中标记为stack,地址从高到低增长,由系统自动管理生命周期。
- 堆区域:
- 存储内容:所有Python对象(整数、列表、字典等),由Python内存分配器(基于arena池机制,并非直接调用系统
malloc)管理。 - 内存特征:在
/proc/[pid]/maps中标记为heap或anon_inode:[python_mmap](Python内存池区域),地址从低到高增长,对象生命周期由GC和引用计数控制。
- 存储内容:所有Python对象(整数、列表、字典等),由Python内存分配器(基于arena池机制,并非直接调用系统
- 边界验证:将
id()返回的地址与/proc/[pid]/maps中的区域对比,即可判断对象属于栈还是堆。比如小整数(如你的代码中5、2)属于Python预缓存的对象,存放在只读内存区域;而动态计算出的对象(如c=a+b的结果)则存放在堆或内存池中。
三、列表与字典的测试提示
测试可变对象时,重点关注其结构体的指针数组:
- 列表:
PyListObject结构体包含ob_item指针数组,每个元素指向列表中元素的对象地址。打印lst = [1,2,3]的id(lst)及每个元素的id(),在dump中定位列表地址后,可找到ob_item数组,验证其中的指针是否与元素id()一致。 - 字典:
PyDictObject的哈希表存储键值对的指针,每个键和值都是独立的Python对象,可通过类似方法定位字典地址,查看哈希表中的指针关联键值对象。
内容的提问来源于stack exchange,提问作者EBAH
相关产品推荐
相关产品推荐

