ctypes对象的内存地址非常相似

时间:2015-11-07 10:03:20

标签: python-3.x ctypes

当我认识到这个有趣的行为时,我正在调试另一个项目:

如果我从python callables生成两个c callables,他们总是在非常相似的位置:

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不是有趣的部分,而是048110

这是否意味着,我的程序总是位于 ram 中非常相似的位置?

信息:我在 linux 3.18.x

2 个答案:

答案 0 :(得分:2)

最低有效数字是相同的,但请注意中间的数字不是。所以,你的程序在不同的运行中在内存中移动,但是正在进行某种代码页对齐,这样地址始终以4096的倍数开始。并不奇怪。

答案 1 :(得分:2)

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。池本身有一个初始头(pool_header),它是48(0x030)字节,而且每个ctypes对象都有一个垃圾收集头(PyGC_Head),它是24(0x018)字节。没有GC头,ctypes函数指针是176个字节(0x0b0)。因此,下一个函数指针的GC头位于偏移量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分配一块可执行内存,在其中写入一些代码,设置调用closure_fcn来调用目标Python函数。这是CThunkObject,引用(保持在线状态),例如c_foo._objects['0']