C-API: выделение "PyTypeObject-extension"
Я нашел код в 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.
Это выглядит очень опасно, и я удивлен, что ошибка осталась незамеченной. Риск состоит в том, что какой-то будущий объект будет помещен в это место (то есть предназначено для хранения 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, а длина выделенного блока памяти должна быть tp_basicsize + nitemstp_itemsize, округленная до кратного sizeof (void); в противном случае nitems не используется, и длина блока должна быть tp_basicsize.
Похоже, я должен установить:
table->tp_itemsize = sizeof(void*);
:
PyObject* tmp = subtype->tp_alloc(subtype,1);
РЕДАКТИРОВАТЬ: только что попробовал это, и это вызывает сбой
Но затем в документации говорится:
Не используйте эту функцию для какой-либо другой инициализации экземпляра, даже для выделения дополнительной памяти; это должно быть сделано с помощью tp_new.
Теперь я не уверен, принадлежит ли этот код в tp_new или tp_init.
Связанные с:
Передача аргументов в tp_new и tp_init из подтипов в Python C API
2 ответа
На самом деле это (незначительная / безвредная) ошибка в PyCXX
SO хотел бы преобразовать этот ответ в комментарий, который не имеет смысла, я не могу присвоить зеленую галочку завершения, поэтому я комментирую. Поэтому я должен бродить, чтобы квалифицировать его. blerh.
Код правильный.
Пока PyTypeObject для объекта расширения правильно инициализирован, он должен работать.
Базовый класс tp_alloc
получает subtype
поэтому он должен знать, сколько памяти выделить, проверив tp_basicsize
член.
Это обычный шаблон Python C/API, как показано в руководстве.