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

use*_*137 9 python ctypes memory-management

假设我使用ctypes模块定义以下变量

i = c_int(4)
Run Code Online (Sandbox Code Playgroud)

然后我试着找出我使用的内存地址:

id(i)
Run Code Online (Sandbox Code Playgroud)

要么

ctypes.addressof(i)
Run Code Online (Sandbox Code Playgroud)

目前,它产生不同的价值.这是为什么?

Jon*_*art 19

您的建议应该是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'
Run Code Online (Sandbox Code Playgroud)

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

在上面的例子中:

   c_int
 ___________
|           |   0x22940d0   This is what id() returns
| metadata  |
|           |
|           |
|           |
|           |
|___________|
|   value   |   0x22940f8   This is what addressof() returns
|___________|
Run Code Online (Sandbox Code Playgroud)

编辑:

在ctypes的CPython实现中,base CDataObject(2.7.6 source)有一个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;
};
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

这个技巧利用了idCPython中返回对象基址的事实.

  • 好的,但为什么它甚至没有记录在cpython中? (2认同)