选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2021-01-21
  • 更新:2021-01-21
使用 C 写 Python 模块时内存回收管理,Py_INCREF() 和 Py_DECREF() 的使用方式, 什么时候需要手动调用 Py_INCREF() 和 Py_DECREF()


查看官方文档: https://docs.python.org/3/extending/extending.html#reference-counts

引用和借用介绍: https://docs.python.org/3/extending/extending.html#ownership-rules

GC 标记回收 注意点

  • 注意借用周期必须要在引用周期内

  • 所有新建对象比如PyLong_FromLong()Py_BuildValue()PyDict_Keys()PyDict_Values()都会将引用 +1, 同样,释放变量引用会自动-1,比如Py_TYPE(self)->tp_free(***) 会自动减引用

    所以在我们使用临时变量时,也需要将其用变量存起来,用完后调用Py_DECREF(),比如下面用法是错误的:

    1. PyList_SetItem(list, 1, PyInt_FromLong(0L));

    这里 PyInt_FromLong 新建对象引用+1, 然后 PyList_SetItem 将新对象引用 +1(后面有讲), 所以新对象共引用了2次,但是临时变量用完后没有将引用-1, 导致最后 list释放时,-1了引用还大于零,即内存泄露,始终无法回收
    正确用法:

    1. PyObject* temp = PyInt_FromLong(0L);
    2. PyList_SetItem(list, 1, temp);
    3. Py_DECREF(temp);
  • 许多从其他对象中提取对象的函数也通过引用来转移所有权, 比如PyObject_GetAttrString(), 引用+1

  • 从对象中提取对象,有些例外PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem(), and PyDict_GetItemString(), 是借用,引用不会+1

  • PyImport_AddModule() 也是借用,引用不会+1(因为引用可以在sys.modules中)

  • 如果你将一个对象赋值给其它变量,比如函数返回,变量赋值,对象赋值, 这都是借用,如果你希望借用者变成引用者,需要手动调用Py_INCREF(),但是有两个意外情况PyTuple_SetItem()PyList_SetItem(), 这两个会获得引用权(引用+1), 注意PyDict_SetItem()只是借用

  • 注意,如果用C写一个函数,返回值必须拥有引用权,而不是借用,这样才能保证调用方能使用这个对象, 所以如果你返回的对象是借来的,那么必须将其转为引用,即调用Py_INCREF()将引用+1

    比如Py_None, 如果直接return Py_None,则有可能出现错误,因为这是一个赋值行为,而且Py_None不是新New出来的, 所以是借用, 需要在返回前增加一次引用Py_INCREF(); return Py_None;

  • 另外,在借用时也要注意,借用期间注意要保证引用不会被减到0,如果没法保证,就自己手动+1,比如

    1. void bug(PyObject *list)
    2. {
    3. PyObject *item = PyList_GetItem(list, 0);
    4. PyList_SetItem(list, 1, PyLong_FromLong(0L));
    5. PyObject_Print(item, stdout, 0); /* BUG! */
    6. }

    这里看起来没什么问题,除了上面提到的PyLong_FromLong的问题, 只设置了列表元素1,而没有改变元素0,那么为什么会出bug呢?
    因为这里设置了元素1,那么之前的元素1就有可能引用为0,可能会触发它的__del__函数,用户如果在这个函数里面也删除了元素0, 那我们在后面调用item 就会失败,因为这个对象有可能已经被回收了, 正确的方式是在设置元素1之前给 item增加一个引用,然后再设置元素1,print结束后再删除item的引用:

    1. void no_bug(PyObject *list)
    2. {
    3. PyObject *item = PyList_GetItem(list, 0);
    4. PyObject* temp = PyLong_FromLong(0L);
    5. Py_INCREF(item);
    6. PyList_SetItem(list, 1, temp);
    7. PyObject_Print(item, stdout, 0);
    8. Py_DECREF(item);
    9. Py_DECREF(temp);
    10. }
  • 如果不确定传入的变量是不是NULL, 使用 Py_XINCREF()Py_XDECREF()

  • 对于PyArg_ParseTupleAndKeywords 解析的参数,如果获得的值是对象,需要对这个对象增加引用然后再使用,这样可以防止外部传进来的变量被销毁,可以参考Python源码中的custom2.c custom3.c``custom4.c

    1. static int
    2. Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    3. {
    4. static char *kwlist[] = {"first", "last", "number", NULL};
    5. PyObject *first = NULL, *last = NULL, *tmp;
    6. if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
    7. &first, &last,
    8. &self->number))
    9. return -1;
    10. if (first) {
    11. tmp = self->first; // 保存旧变量
    12. Py_INCREF(first); // 增加参数对象的引用
    13. self->first = first; // 赋值
    14. Py_DECREF(tmp); // 减少旧变量的引用
    15. }
    16. if (last) {
    17. tmp = self->last;
    18. Py_INCREF(last);
    19. self->last = last;
    20. Py_DECREF(tmp);
    21. }
    22. return 0;
    23. }
文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-j5lk95.jpg