当我意识到这个有趣的行为时,我正在调试另一个项目:
如果我从 python 可调用对象生成两个 c 可调用对象,它们总是位于非常相似的位置:
from ctypes import *
def foo():
print("foo")
def bar():
print("bar")
c_cm=CFUNCTYPE(c_voidp)
c_foo=c_cm(foo)
print(c_foo)
c_bar=c_cm(bar)
print(c_bar)
运行几次:
<CFunctionType object at 0x7f8ddb65d048>
<CFunctionType object at 0x7f8ddb65d110>
<CFunctionType object at 0x7f40a022e048>
<CFunctionType object at 0x7f40a022e110>
<CFunctionType object at 0x7fa1f1fb1048>
<CFunctionType object at 0x7fa1f1fb1110>
7f
不是有趣的部分,而是 048
和 110
。
这是否意味着我的程序始终位于ram中非常相似的位置?
信息:我使用的是 linux 3.18.x
最佳答案
CPython 的小对象分配器使用 256 KB 的区域,分为 4 KB 的池,其中给定的池专用于特定的分配大小(范围从 8 到 512 字节,步长为 8)。地址的低 3 位十六进制数字(12 位)是对象在池中的偏移量。此设计在 Objects/obmalloc.c 中的大量评论中进行了讨论。 .
对于 64 位 Linux,一个 ctypes 函数指针对象为 200 (0xc8) 字节,即 sys.getsizeof(c_bar) == 200
,因此一个池可以容纳 20 个函数指针。请注意,池中第一个分配的对象位于偏移量 0x048 而不是 0x000。池本身有一个 48 (0x030) 字节的初始 header ( pool_header
),另外每个 ctypes 对象都有一个 24 (0x018) 字节的垃圾收集 header ( PyGC_Head
)。如果没有 GC header ,ctypes 函数指针为 176 字节 (0x0b0)。因此,下一个函数指针的 GC header 位于偏移量 0x0f8 处,对象本身在 24 个字节后的偏移量 0x110 处开始。
一旦开始从完全空闲的池中分配,您就可以打印一堆来查看模式。例如,funcs = [c_cm(foo) for i in range(10000)][-40:];
idx = 0; while id(funcs[idx]) & 0xfff != 0x048: idx +=1;
print(*[funcs[n] for n in range(idx, idx+20)], sep='\n')
:
<CFunctionType object at 0x7f66ca3df048>
<CFunctionType object at 0x7f66ca3df110>
<CFunctionType object at 0x7f66ca3df1d8>
<CFunctionType object at 0x7f66ca3df2a0>
<CFunctionType object at 0x7f66ca3df368>
<CFunctionType object at 0x7f66ca3df430>
<CFunctionType object at 0x7f66ca3df4f8>
<CFunctionType object at 0x7f66ca3df5c0>
<CFunctionType object at 0x7f66ca3df688>
<CFunctionType object at 0x7f66ca3df750>
<CFunctionType object at 0x7f66ca3df818>
<CFunctionType object at 0x7f66ca3df8e0>
<CFunctionType object at 0x7f66ca3df9a8>
<CFunctionType object at 0x7f66ca3dfa70>
<CFunctionType object at 0x7f66ca3dfb38>
<CFunctionType object at 0x7f66ca3dfc00>
<CFunctionType object at 0x7f66ca3dfcc8>
<CFunctionType object at 0x7f66ca3dfd90>
<CFunctionType object at 0x7f66ca3dfe58>
<CFunctionType object at 0x7f66ca3dff20>
请注意,repr 中打印的函数指针对象的基地址与传递给 C 库的地址没有直接关系。函数指针对象(即 PyCFuncPtrObject
)有一个 b_ptr
字段指向一个缓冲区,该缓冲区保存传递给 C 的实际函数地址。您可以通过创建 void *
来检查该值。来自函数指针的指针,例如addr_bar = c_void_p.from_buffer(c_bar).value
。对于回调,ctypes 分配一 block 可执行内存,在其中写入一些设置调用 closure_fcn
的代码。调用目标Python函数。这是 CThunkObject
,它被引用(保持事件状态),例如 c_foo._objects['0']
.
关于python-3.x - ctypes 对象位于非常相似的内存地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33581455/