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

标签 python memory-management ctypes

假设我使用 ctypes module 定义了以下变量

i = c_int(4)

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

id(i)

ctypes.addressof(i)

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

最佳答案

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

id()功能:

Return the “identity” of an object. This is an integer which is guaranteed to be unique and constant for this object during its lifetime.

CPython implementation detail: This is the address of the object in memory.

虽然它们在 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字节。反复试验几次,我们可以看到情况总是如此。因此,我会说在整个 c_int 中实际 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 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;
};

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 对象使用 CDataObject 的默认 16 字节 b_value 成员。如上例所示,此默认缓冲区用于 c_int(4) 实例。我们可以打开 ctypes 自身以在 32 位进程中自省(introspection) 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 返回对象基地址的事实。

关于python - CPython 中的 id(obj) 和 ctypes.addressof(obj) 有什么区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23600052/

相关文章:

Python正则表达式捕获短语?

c - 在c中循环整数数组时出错

memory-management - 为什么要在 rebol 中分配一个变量?

python - 从 Python 使用 C

python - ctypes.create_string_buffer Python 2 与 Python 3

python - 如何在 python 中可靠地进行清理?

Python Tornado 获取 URL 参数

python - 为什么不能更改对象实例的__class__属性?

python - 使用 Python 描述符触发类成员的内容

c - 用于 malloc() 的这个大小是正确的吗?