python引用计数的原理_python扩展——引用计数与内存泄露 | 学步园

转载请注明出处:

背景

Python往往包含大量的内存分配和释放,同样需要避免内存泄漏(对于分配的内存忘记释放)和野指针(读写没有分配的内存区域)。他选择的方法就是 引用计数 。其原理比较简单:每个对象都包含一个计数器,计数器的增减与引用的增减直接相关,当引用计数为0时,表示对象已经没有存在的意义,可以删除了。在pyhton扩展中,C/C++语言来操作python对象的引用计数是一个非常有趣也非常容易出现错误的事情。对于拥有的引用,在不再需要时负责调用 Py_DECREF() 来减少引用计数。忘记减少拥有的引用计数会导致内存泄漏,忘记增加拥有的引用计数可能会导致解释器崩溃,那什么时候需要增减引用计数呢?

拥有权规则

任何人都无法拥有一个对象的所有权,但是可以拥有它的引用。

一个对象的引用无论何时传入或传出一个函数,它都是函数接口规范的一部分,而与所有权被转让与否无关。

大多数返回一个对象引用的函数通过引用转让所有权。尤其是那些功能就是创建新对象的函数,例如PyInt_FromLong()和Py_BuildValue(),会转让所有权给接收者。即使对象实际是不是新创建的,你接收到的仍然是对象的一个新的引用。例如:PyInt_FromLong()维护了常用值的一个缓存,能返回已缓存对象。

需要说明的另外一个概念叫做“借用”对象,顾名思义,借用的对象不需要对它的引用计数加1,所以更不能调用 Py_DECREF() 来减少引用计数。借用的优点是你无需管理引用计数,缺点是可能被野指针搞的头晕,因为你并不能保证借用的这个对象在使用时是不是已经被释放了。PyTuple_GetItem()、PyList_GetItem()、PyDict_GetItem()、和PyDict_-GetItemString()都是返回你从元组、列表或字典借得的引用。

如果一个对象引用被传入另一个函数,一般而言,是函数从你那借用引用——如果函数需要存储引用的话,它要使用Py_INCREF()以成一个独立的所有者。对这一规则,有两个重要的例外:PyTuple_SetItem()和PyList_SetItem()。这两个函数接管传给它们的子项的所有权——即使这些子项失效了!(PyDict_SetItem()那类函数不接管所有权)

当一个C函数在Python中被调用,它从调用者那里借用自已参数的引用。调用者拥有对象的一个引用,所以“借引用”的寿命在返回值退出之前都有保证。仅当这样的“借引用”要被存储或传递时,才要通过调用Py_INCREF()来转换成“拥有引用”。

在Python调用的C函数返回的对象引用必须是“拥有引用”——所有权由函数转让给了它的调用者

常见的一些错误

明显的引用错误

PyObject*_list = PyList_New( 0 );

PyObject*_list1 = PyList_New( 0 );

return_list;

由于_list1被创建后,既没有被释放(调用Py_DECREF())也没有返回(return转让所有权),所以导致内存泄露。

PyList_Append()

(这个函数说多了都是泪,被他折腾了半天才查到内存泄露的原因。)

一些情况下,扩展函数需要向python返回一个list,于是会有这种写法:

Intvar = …;

PyList_Append(_list,PyInt_FromLong(var);

Return_list;

看上去并没什么毛病,但是追查一番发现问题出现在PyList_Append身上,他会对第二个参数的引用计数加1,在这个例子中PyInt_FromLong(var)返回一个对象,它的引用是1,PyList_Append把这个对象加入到list后,引用计数为2。之后将_list拥有权转交给python调用方。后面可想而知了,调用方在释放这个List的时候只会对计数减一,也就是说该对象计数永远无法到0(也就意味着内存泄露了)。

解决的办法有两个:

办法1

PyObject*_list = PyList_New( 0 );

Int var= …;

PyObject * tmp = PyInt_FromLong(var);PyList_Append(_list,tmp);

Py_DECREF(jpo);…

Return_list;

办法2

PyObject*_list = PyList_New( CNT );

Int var= …;

PyList_SetItem(_list,0, PyInt_FromLong(var));

Return_list;

其中CNT表示指定list的长度,然后依次去对每一个下标元素赋值,而不是再采用APPEND的方法。至于为何PyList_SetItem可以解决这个问题,可以参加“拥有权规则哪里的介绍”

其他错误

等待大家的补充^_^。

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值