引用計數法
增量操作
如果對象的引用數量增加,就在該對象的計數器上進行增量操作。在實際中它是由宏Py_INCREF() 執行的。
#define Py_INCREF(op) (((PyObject*)(op))->ob_refcnt++)
#define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)
除了增量操作外,還要執行NULL檢查,Py_XINCREF(op)。
計數器溢出的問題
Include/object.h
typedef ssize_t Py_ssize_t;
ssize_t型,在32位環境下是int在64位下是long,它的大小由系統決定。這里定義的計數器它是可以為負數的,那么就有問題了,計數器是有符號整數,他能表達的最大數僅僅是無符號整數的一半。這樣不會內存溢出嗎?我們之前說對象是4字節對齊的,既然是按4字節對齊,我們就可以得到這樣分下來,即使所有對象都指向某一個對象,也是不會溢出的。
那么,負的計數器表達的是什么?在debug中,會存在減數操作過度,和增量操作遺失的情況。負的計數器就是為它而設計的。在debug中,Py NegativeRefcount() 函數會把變為負數的對象信息當成錯誤信息輸出。
減量操作
- 先將計數器減量
- 如果得出0以外的數值就調用_Py_CHECK_REFCNT()。它負責檢查引用計數器是否變為負數。
- 如果計數器為0就調用 _Py_Dealloc(),與增量操作相同,這里是減量操作。
- NULL檢查擴展的減量操作。
其中成員 tp_dealloc 存着負責釋放各個對象的函數指針,比如下面這個釋放元組對象的函數指針。
Objects/tupleobject.c
static void tupledealloc(register PyTupleObject *op)
{
register Py_ssize_t i;
register Py_ssize_t len = Py_SIZE(op);
if (len > 0) {
i = len;
/* 將元組內的元素進行減量 */
while (--i >= 0)
Py_XDECREF(op->ob_item[i]);
}
/* 釋放元組對象 */
Py_TYPE(op)->tp_free((PyObject *)op);
Py_TRASHCAN_SAFE_END(op) }
- 先對元組進行減量,然后在去釋放對象。
- 成員tp_free里存着各個對象的釋放處理程序。調用PyObject_GC_Del()
PyObject_GC_Del()
void PyObject_GC_Del(void *op) {
PyGC_Head *g = AS_GC(op);
/* 省略部分:釋放前的處理 */
PyObject_FREE(g);
}
這里的 PyObject_FREE(),就是上一節中的 PyObject_Free()函數,這個函數會對對象進行釋放。不過我是怎么知道的呢。此處又有宏定義。#define PyObject_FREE PyObject_Free。位於Include/objimpl.h
元組減量操作如下圖示:

終結器
就是我們類里經常寫的 __del__
終結器指的是與對象的釋放處理掛鈎的一個功能。列表和字典等內置對象基本上是不能設置終結器的,能定義終結器的只有用戶創建的類。
# 一個終結器
class Foo(object):
def __def__(self): # 定義終結器
print("GKD")
這種情況下,當Foo被釋放的時候,就會輸出GKD。
那么Foo實例實際上是怎么調用的呢?如下示:

Objects/typeobject.c:subtype_dealloc():單獨拿出終結器的部分
static void subtype_dealloc(PyObject *self)
{
PyTypeObject *type, *base;
destructor basedealloc;
type = Py_TYPE(self);
if (type->tp_del) {
_PyObject_GC_TRACK(self);
type->tp_del(self);
}
/* 省略 */
}
實例的情況下,變量 tp_del 中保存着執行終結器所需的 slot_tp_del() 函數
Objects/typeobject.c:slot_tp_del()
static void
slot_tp_del(PyObject *self)
{
static PyObject *del_str = NULL;
PyObject *del, *res;
self->ob_refcnt = 1;
/* 如果有__del__就執行它 */
del = lookup_maybe(self, "__del__", &del_str);
if (del != NULL) {
res = PyEval_CallObject(del, NULL);
/* 省略部分:錯誤檢查和后處理等 */
}
if (--self->ob_refcnt == 0)
return; /* 退出函數 */
/* 省略部分:最終化時有引用的情況下的應對處理 */
}
先用lookup_maybe(),取出實例中的__del__,然后使用 PyEval_CallObject()來執行它。
插入計數處理
在python中,正常情況是要對對象的計數器進行增量和減量操作的。但是並不是所有地方都需要這樣做。
比如說在python中編寫c的擴展模塊:當從局部變量引用某個對象,大多數情況下是可以不執行計數處理的,因為從局部來說,我們引用它之后給計數器增量,退出后局部后又要減量。這實際上沒有任何意義。不過也可以這樣做。
本來計數器的作用是告訴GC這個對象被引用了,不要回收。那如果計數器的值已經是大於0了。我們還需要這樣的增量計數器嗎?增量之后計數器局部使用完后還是會被減量的。
但是在局部變量的作用域中,如果對象的計數器為0那就必須要進行增量操作對變量進行保護了。
像這樣的情況,何時對對象的計數器增量,何時減量,完全可以有編程人員自己判斷,如果不能判斷則就按照規則來。
