Python引用計數與相關析構函數的實際操作步驟
在C或是在C++中,相關人員在實際的操作中是可以自由的運用,但是在某些方面可以說是存在一些罪惡,就這一缺陷,我們可以用Python引用計數在其方面有一個解決的方案,以下是文章的具體介紹。
在權利的面,程序員必須負責將申請的內存釋放,并釋放無效指針。可以說,這一點正是萬惡之源,大量內存泄露和懸空指針的bug由此而生,如黃河泛濫一發不可收拾。
現代的開發語言中一般都選擇由語言本身負責內存的管理和維護,即采用了垃圾收集機制,比如Java和C#。垃圾收集機制使開發人員從維護內存分配和清理的繁重工作中解放出來,但同時也剝奪了程序員與內存親密接觸的機會,并付出了一定的運行效率作為代價。#t#
現在看來,隨著垃圾收集機制的完善,對時間要求不是非常高的程序完全可以通過使用垃圾收集機制的語言來完成,這部分程序占了現存的大多數的程序。這樣做的好處是提高了開發效率,并降低了bug發生的幾率。Python同樣也內建了垃圾收集機制,代替程序員進行繁重的內存管理工作,而引用計數正是Python垃圾收集機制的一部分。
Python通過對一個對象的引用計數的管理來維護對象在內存中的存在與否。我們知道在Python中每一個東西都是一個對象,都有一個ob_refcnt變量。這個變量維護著該對象的引用計數,從而也最終決定著該對象的創建與消亡。
在Python中,主要是通過Py_INCREF(op)和Py_DECREF(op)兩個宏來增加和減少一個對象的Python引用計數。當一個對象的引用計數減少到0之后,Py_DECREF將調用該對象的析構函數來釋放該對象所占有的內存和系統資源。注意這里的“析構函數”借用了C++的詞匯,實際上這個析構動作是通過在對象對應的類型對象中定義的一個函數指針來指定的,就是那個tp_dealloc。
如果熟悉設計模式中的Observer模式,就可以看到,這里隱隱約約透著Observer模式的影子。在ob_refcnt減為0之后,將觸發對象銷毀的事件。從Python的對象體系來看,各個對象提供了不同的事件處理函數,而事件的注冊動作正是在各個對象對應的類型對象中靜態完成的。
PyObject中的ob_refcnt是一個32位的整形變量,這實際蘊含著Python所做的一個假設,即對一個對象的引用不會超過一個整形變量的最大值。一般情況下,如果不是惡意代碼,這個假設顯然是成立的。
需要注意的是,在Python的各種對象中,類型對象是超越引用計數規則的。類型對象“跳出三界外,不再五行中”,永遠不會被析構。每一個對象中指向類型對象的指針不被視為對類型對象的引用。在每一個對象創建的時候,Python提供了一個_Py_NewReference(op)宏來將對象的Python引用計數初始化為1。
在Python的源代碼中可以看到,在不同的編譯選項下(Py_REF_DEBUG, Py_TRACE_ REFS),引用計數的宏還要做許多額外的工作。下面展示的代碼是Python在最終發行時這些宏所對應的實際的代碼:
- [object.h]
- #define _Py_NewReference(op) ((op)->ob_refcnt = 1)
- #define _Py_Dealloc(op) ((*(op)->ob_type->tp_dealloc)((PyObject *)(op)))
- #define Py_INCREF(op) ((op)->ob_refcnt++)
- #define Py_DECREF(op) \
- if (--(op)->ob_refcnt != 0) \
- ; \
- else \
- _Py_Dealloc((PyObject *)(op))
- /* Macros to use in case the object pointer may be NULL: */
- #define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)
- #define Py_XDECREF(op) if ((op) == NULL) ; else Py_DECREF(op)
在一個對象的Python引用計數減為0時,與該對象對應的析構函數就會被調用,但是要特別注意的是,調用析構函數并不意味著最終一定會調用free釋放內存空間,如果真是這樣的話,那頻繁地申請、釋放內存空間會使Python的執行效率大打折扣(更何況Python已經多年背負了人們對其執行效率的不滿)。
一般來說,Python中大量采用了內存對象池的技術,使用這種技術可以避免頻繁地申請和釋放內存空間。因此在析構時,通常都是將對象占用的空間歸還到內存池中。這一點在接下來對Python內建對象的實現中可以看得一清二楚。