【问题标题】:ctypes objects at very similar memory addressctypes 在非常相似的内存地址的对象
【发布时间】:2015-11-07 10:03:20
【问题描述】:

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

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

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

信息:我在 linux 3.18.x

【问题讨论】:

  • 顺便说一句,名称 c_voidp 已弃用。请在新代码中使用c_void_p
  • @eryksun 回答,我会投票!

标签: python-3.x ctypes


【解决方案1】:

CPython 的小对象分配器使用 256 KB 的区域,分为 4 KB 的池,其中给定的池专用于特定的分配大小(范围从 8 到 512 字节,以 8 为步长)。地址的低 3 个十六进制数字(12 位)是对象在池中的偏移量。此设计在Objects/obmalloc.c 中的大量 cmets 中进行了讨论。

在 64 位 Linux 的情况下,一个 ctypes 函数指针对象是 200 (0xc8) 字节,即sys.getsizeof(c_bar) == 200,所以一个池包含 20 个函数指针。请注意,池中第一个分配的对象位于偏移量 0x048 而不是 0x000。池本身有一个 48 (0x030) 字节的初始标头 (pool_header),而且每个 ctypes 对象都有一个 24 (0x018) 字节的垃圾回收标头 (PyGC_Head)。如果没有 GC 标头,则 ctypes 函数指针为 176 字节 (0x0b0)。因此,下一个函数指针的 GC 标头位于偏移量 0x0f8 处,对象正确地在 24 字节后的偏移量 0x110 处开始。

一旦开始从完全空闲的池中分配,您可以打印出一堆来查看模式。比如funcs = [c_cm(foo) for i in range(10000)][-40:];idx = 0; while id(funcs[idx]) &amp; 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']

【讨论】:

    【解决方案2】:

    最低有效数字相同,但请注意中间的数字不同。因此,您的程序在不同的运行时在内存中移动,但存在某种代码页对齐方式,因此地址总是以 4096 的倍数开始。这并不奇怪。

    【讨论】:

    • 这就是我想听到的,代码页对齐
    猜你喜欢
    • 2018-09-08
    • 2023-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多