CPython中的id(obj)和ctypes.addressof(obj)有什么区别

时间:2014-05-12 02:09:01

标签: python memory-management ctypes

假设我使用ctypes module

定义以下变量
i = c_int(4)

然后我尝试使用以下方法找出i的内存地址:

id(i)

ctypes.addressof(i)

目前,产生不同的价值。那是为什么?

1 个答案:

答案 0 :(得分:18)

您建议的情况应该是CPython的实现细节。

id()功能:

  

返回对象的“标识”。这是一个整数,在该生命周期内保证该对象是唯一且恒定的。

     

CPython实现细节:这是内存中对象的地址。

虽然它们在CPython中可能是等效的,但在其他Python实现中并不能保证这一点。


为什么它们的值不同,即使在CPython中也是如此?

请注意c_int

  • Python对象。 CPython的id()将返回此地址。

  • 包含 4字节C兼容int值。 ctypes.addressof()将返回此地址。

Python对象中的元数据占用空间。因此,这个4字节的值可能不会出现在Python对象的最开头。

看看这个例子:

>>> import ctypes
>>> i = ctypes.c_int(4)
>>> hex(id(i))
'0x22940d0'
>>> hex(ctypes.addressof(i))
'0x22940f8'

我们看到addressof结果仅比id()的结果高0x28字节。玩了几次,我们可以看到情况总是这样。因此,我要说在整个int的实际c_int值之前有0x28字节的Python对象元数据。

在上面的例子中:

   c_int
 ___________
|           |   0x22940d0   This is what id() returns
| metadata  |
|           |
|           |
|           |
|           |
|___________|
|   value   |   0x22940f8   This is what addressof() returns
|___________|

修改

在ctypes的CPython实现中,基类CDataObject(2.7.6源)有一个b_ptr成员,指向用于对象C数据的内存块:

union value {
                char c[16];
                short s;
                int i;
                long l;
                float f;
                double d;
#ifdef HAVE_LONG_LONG
                PY_LONG_LONG ll;
#endif
                long double D;
};

struct tagCDataObject {
    PyObject_HEAD
    char *b_ptr;                /* pointer to memory block */
    int  b_needsfree;           /* need _we_ free the memory? */
    CDataObject *b_base;        /* pointer to base object or NULL */
    Py_ssize_t b_size;          /* size of memory block in bytes */
    Py_ssize_t b_length;        /* number of references we need */
    Py_ssize_t b_index;         /* index of this object into base's
                                   b_object list */
    PyObject *b_objects;        /* dictionary of references we need 
                                   to keep, or Py_None */
    union value b_value;
};

addressof将此指针作为Python整数返回:

static PyObject *
addressof(PyObject *self, PyObject *obj)
{
    if (CDataObject_Check(obj))
        return PyLong_FromVoidPtr(((CDataObject *)obj)->b_ptr);
    PyErr_SetString(PyExc_TypeError,
                    "invalid type");
    return NULL;
}

小C对象使用b_value的默认16字节CDataObject成员。如上例所示,此默认缓冲区用于c_int(4)实例。我们可以将ctypes自身转换为在32位进程中内省c_int(4)

>>> i = c_int(4)
>>> ci = CDataObject.from_address(id(i))

>>> ci
ob_base: 
    ob_refcnt: 1
    ob_type: py_object(<class 'ctypes.c_long'>)
b_ptr: 3071814328
b_needsfree: 1
b_base: LP_CDataObject(<NULL>)
b_size: 4
b_length: 0
b_index: 0
b_objects: py_object(<NULL>)
b_value: 
    c: b'\x04'
    s: 4
    i: 4
    l: 4
    f: 5.605193857299268e-45
    d: 2e-323
    ll: 4
    D: 0.0

>>> addressof(i)
3071814328
>>> id(i) + CDataObject.b_value.offset
3071814328

这个技巧利用了CPython中id返回对象基址的事实。