CPython中id(obj)和ctypes.addressof(obj)有什么区别
假设我用 ctypes模块 定义了一个变量。
i = c_int(4)
然后我想通过以下方式找出这个变量 i 的内存地址:
id(i)
或者
ctypes.addressof(i)
这两种方法现在得到的结果却不一样。为什么会这样呢?
1 个回答
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'
我们看到addressof
的结果比id()
的结果高出0x28字节。多试几次,我们会发现这种情况总是如此。因此,我认为在整体c_int
中,实际的int
值前面有0x28字节的Python对象元数据。
在我上面的例子中:
c_int
___________
| | 0x22940d0 This is what id() returns
| metadata |
| |
| |
| |
| |
|___________|
| value | 0x22940f8 This is what addressof() returns
|___________|
编辑:
在CPython的ctypes实现中,基本的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对象使用CDataObject
的默认16字节b_value
成员。正如上面的例子所示,这个默认缓冲区用于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
返回对象基本地址的事实。