Как вернуть кэшированное значение PyObject * несколько раз без утечки памяти или двойного / тройного освобождения? - PullRequest
0 голосов
/ 23 мая 2019

На мой последний вопрос Как улучшить чтение строки файла Python C Extensions? поднял некоторые проблемы с памятью, которые у меня были. Затем я написал это простое и нелепое использование Python C Extension просто для того, чтобы попытаться лучше понять ссылки на заимствования и владение Python.

static void PyFastFile_dealloc(PyFastFile* self) {
    for( PyObject* pyobject : linecache ) {
        Py_XDECREF( pyobject );
    }
    Py_TYPE(self)->tp_free( (PyObject*) self );
}

static PyObject* PyFastFile_tp_iter(PyFastFile* self, PyObject* args) {
    counter = 10;
    std::string line{"sample line"}
    PyObject* obj = PyUnicode_DecodeUTF8( line.c_str(), line.size(), "replace" );
    linecache.push_back( obj );
}

static PyObject* PyFastFile_iternext(PyFastFile* self, PyObject* args) {
    --counter;

    if( !( counter ) ) {
        PyErr_SetNone( PyExc_StopIteration );
        return NULL;
    }

    PyObject* retval = linecache[0];
    Py_XINCREF( retval );
    return retval;
}

// create the module
PyMODINIT_FUNC PyInit_fastfilepackage(void) {
    PyFastFileType.tp_iter = (getiterfunc) PyFastFile_tp_iter;
    PyFastFileType.tp_iternext = (iternextfunc) PyFastFile_iternext;
    PyFastFileType.tp_dealloc = (destructor) PyFastFile_dealloc;
    ...
}

В этом случае

  1. Возвращает ли tp_next() принадлежащую ссылку на linecache[0], поскольку она увеличивает Py_XINCREF?
  2. Это означает, что мой linecache[0] кеш теперь является заимствованной ссылкой?
  3. Поскольку tp_next() вызывается более одного раза, и я возвращаю один и тот же указатель несколько раз, увеличивая его Py_XINCREF, это приведет к двойному / тройному / нескольким освобождениям?
  4. Имеет ли tp_next() возвращаемый объект только одну принадлежащую ссылку, что приведет к двойному / тройному / нескольким?

Связанный:

  1. Py_INCREF / DECREF: когда

1 Ответ

1 голос
/ 26 мая 2019

Вам нужно, чтобы количество ссылок объекта было равно числу PyObject*, которое ссылается на него.

  1. Да - вы возвращаете PyObject* в свой код Python, поэтому вам следует увеличить счетчик ссылок.

  2. Нет - когда создается linecache[0], он имеет повторный счет 1, и это означает владение linecache. Несколько мест могут «владеть» одним объектом Python.

  3. Да, вы возвращаете один и тот же указатель несколько раз; Нет, это не приведет к множественным свободам. Указатель освобождается, когда счетчик ссылок достигает 0. Это произойдет, когда вы прервете все ссылки на значения, которые вы вернули из next, и когда вы потеряете ссылку в linecache (когда вызывается PyFastFile_dealloc) .

  4. Я не понимаю последний вопрос, но код здесь в основном правильный.


Единственная проблема, которую я вижу здесь, это «что такое linecache / кому она принадлежит». Если это глобальная переменная, то она может быть разделена между несколькими PyFastFile объектами, что, вероятно, неправильно. Уничтожение одного PyFastFile приведет к освобождению всего linecache, но вы не pop_back или NULL указатели.

...