Python中的引用計數法

引用計數法

增量操做

若是對象的引用數量增長,就在該對象的計數器上進行增量操做。在實際中它是由宏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.hdebug

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 存着負責釋放各個對象的函數指針,好比下面這個釋放元組對象的函數指針。code

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()blog

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那就必需要進行增量操做對變量進行保護了。

像這樣的狀況,什麼時候對對象的計數器增量,什麼時候減量,徹底能夠有編程人員本身判斷,若是不能判斷則就按照規則來。

相關文章
相關標籤/搜索