Используется ли код C для создания Python расширений UB в C ++ из-за времени жизни? - PullRequest
1 голос
/ 26 мая 2020

Существует множество руководств о том, как создавать C расширений Python, которые вводят новый тип. Один пример: https://docs.python.org/3.5/extending/newtypes.html

Обычно это сводится к созданию такой структуры, как:

struct Example
{
    PyObject_HEAD
    // Extra members
};

, а затем регистрации ее в модуле путем неявного или явного определения функции сопоставления указателей. Связанные со сроком службы: tp_alloc, tp_new, tp_init, tp_free, tp_dealloc.

Из того, что я понимаю, как это работает, PyObject_HEAD расширяется до PyObject ob_base;, что делает Example* и PyObject* конвертируемыми (я думаю, есть некоторые особые формулировка, если это первый член), поэтому весь код принимает PyObject* и может работать с ним, как если бы использовалось struct Example: public PyObject{};. Пока все хорошо.

Но теперь проблема заключается в сроке жизни, если Example: После некоторого копания кажется, что происходит следующее:

  • tp_new вызывается с " type_info "(отображение указателя на функцию) объекта для создания
  • это вызывает tp_alloc, который по умолчанию (в основном) malloc
  • , затем tp_init вызывается с указателем памяти из tp_new, который, например, заполняет счетчик ссылок
  • при уничтожении tp_dealloc называется
  • это вызывает tp_free (в основном free)

Итак, что такое очевидно отсутствует вызов конструктора и деструктора, что на практике нормально, если структура представляет собой POD

Однако недавние стандарты C ++ ясно дали понять, что простого удаления объекта недостаточно, см., например, std::launder и связанные с этим обсуждения.

Следовательно, компиляция такого C расширения как C ++ уже UB? Если нет, то для POD есть особое правило, так что они будут в безопасности, не так ли? Есть ли какие-либо ссылки для разъяснения?

Есть ли документация по безопасному способу создания типов, отличных от POD, эффективным способом? Т.е. не добавлять указатель к объекту Example POD выше, который указывает на этот объект, не являющийся POD, который затем создается с помощью new или аналогичного.

Из описания и ответа на Должен { tp_allo c, tp_dealloc} и {tp_new, tp_free} следует рассматривать как пары? Я бы выделил, что tp_new мог бы сделать new и вернуть его, или позвонить tp_alloc и выполнить новое размещение на вернул память и вернул то. Для меня это звучит как требование «только столько дополнительной инициализации, сколько абсолютно необходимо». Затем tp_dealloc вызовет деструктор и направит его на tp_free. Звучит неплохо, но может быть проблема c, если выравнивание возвращаемой памяти tp_alloc неверно?

Есть ли гарантии, что tp_new и tp_dealloc вызываются ровно один раз?

Некоторый псевдокод для программистов, отличных от Python, согласно приведенному выше описанию:

PyObject* tp_alloc(size_t n){ return malloc(n); }
PyObject* tp_new(PyTypeObject* typeInfo){ return typeinfo->tp_alloc(typeinfo->object_size); }
PyObject* tp_init(PyTypeObject* typeInfo, PyObject* o){ o->typeInfo = typeInfo; o->refCnt = 1; return o }
void tp_dealloc(PyObject* o){ o->typeInfo->tp_free(o); }
void tp_free(void* m){ free(m); }

//User code
struct Example
{
    PyObject obj;
    // Extra members
};
void register(){
  PyTypeObject info = {.tp_alloc = tp_alloc, .tp_new = tp_new, .object_size = sizeof(Example), ...}
  PythonRegister("Example", info);
}

Обратите внимание, что это упрощено. Затем Python будет использовать объект info всякий раз, когда создается / используется тип имени «Пример». И вы можете переопределить все функции и преобразовать между Example* и PyObject*, хотя наследования нет, поскольку они «взаимопреобразуемы по указателю»:

один из них является объектом класса стандартного макета и другой - это первый нестатический c элемент данных этого объекта https://en.cppreference.com/w/cpp/language/static_cast

Моя идея заключалась в том, чтобы переопределить значение tp_new по умолчанию примерно таким:

PyObject* Example_new(PyTypeObject* typeInfo){ return new(typeinfo->tp_alloc(typeinfo->object_size)) Example; }

Что я хотел знать, требуется ли это и действительно ли это вообще.

...