Python引用计数与相关析构函数的实际操作步骤
在C或是在C++中,相关人员在实际的操作中是可以自由的运用,但是在某些方面可以说是存在一些罪恶,就这一缺陷,我们可以用Python引用计数在其方面有一个解决的方案,以下是文章的具体介绍。
在权利的面,程序员必须负责将申请的内存释放,并释放无效指针。可以说,这一点正是万恶之源,大量内存泄露和悬空指针的bug由此而生,如黄河泛滥一发不可收拾。
现代的开发语言中一般都选择由语言本身负责内存的管理和维护,即采用了垃圾收集机制,比如Java和C#。垃圾收集机制使开发人员从维护内存分配和清理的繁重工作中解放出来,但同时也剥夺了程序员与内存亲密接触的机会,并付出了一定的运行效率作为代价。
现在看来,随着垃圾收集机制的完善,对时间要求不是非常高的程序完全可以通过使用垃圾收集机制的语言来完成,这部分程序占了现存的大多数的程序。这样做的好处是提高了开发效率,并降低了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)