我在PyCXX中发现了一些可能有问题的代码。
它确实是一个错误,如果是这样,解决它的正确方法是什么?
问题在于:
struct PythonClassInstance
{
PyObject_HEAD
ExtObjBase* m_pycxx_object;
}
:
{
:
table->tp_new = extension_object_new; // PyTypeObject
:
}
:
static PyObject* extension_object_new(
PyTypeObject* subtype, PyObject* args, PyObject* kwds )
{
PythonClassInstance* o = reinterpret_cast<PythonClassInstance *>
( subtype->tp_alloc(subtype,0) );
if( ! o )
return nullptr;
o->m_pycxx_object = nullptr;
PyObject* self = reinterpret_cast<PyObject* >( o );
return self;
}
现在PyObject_HEAD扩展为“PyObject ob_base;”,所以很明显PythonClassInstance简单地扩展PyObject以包含一个额外的指针(它将指向PyCXX对这个PyObject的表示)
tp_alloc分配用于存储PyObject的内存
然后代码将此指针强制转换为PythonClassInstance,声称它不拥有额外的4(或8?)字节!
然后它将这个额外的内存设置为0。
这看起来非常危险,我很惊讶这个bug已经被忽视了。风险在于某些未来的对象将被放置在这个位置(这意味着存储ExtObjBase *)。
如何解决?
PythonClassInstance foo{};
PyObject* tmp = subtype->tp_alloc(subtype,0);
// !!! memcpy sizeof(PyObject) bytes starting from location tmp into location (void*)foo
但是我想现在也许我需要释放tmp,我不认为我应该像这样直接玩内存。我觉得它可能会危害Python内置管理/内置机器的垃圾收集。
另一种选择是,我可以说服tp_alloc分配4个额外字节(或现在是8;足够指针)绕过1而不是0。
文档说第二个参数是“Py_ssize_t nitems”和:
如果类型的tp_itemsize不为零,则为对象的ob_size字段 应该初始化为nitems和分配的内存的长度 block应为tp_basicsize + nitems tp_itemsize,向上舍入为a sizeof的多个(void );否则,不使用nitems和 块的长度应为tp_basicsize。
所以看起来我应该设置:
table->tp_itemsize = sizeof(void*);
:
PyObject* tmp = subtype->tp_alloc(subtype,1);
编辑:刚刚试过这个并导致崩溃
然后文件继续说:
不要使用此函数进行任何其他实例初始化,而不是 甚至分配额外的内存;这应该由tp_new完成。
现在我不确定这段代码是属于tp_new还是tp_init。
相关:
Passing arguments to tp_new and tp_init from subtypes in Python C API
答案 0 :(得分:1)
代码是正确的。
只要扩展对象的PyTypeObject被正确初始化,它就应该有效。
基类tp_alloc
收到subtype
因此它应该通过检查tp_basicsize
成员知道要分配多少内存。
这是tutorial中所示的常见Python C / API模式。
答案 1 :(得分:0)
实际上这是一个(轻微的/无害的)bug in PyCXX
所以我希望将这个答案转换为评论,这没有任何意义,我无法授予完成的绿色标记,所以我发表评论。所以我必须絮絮叨叨才能获得资格。 blerh。