现在,让我们荡起双桨,哦不对,让我们挽起衣袖和裤脚,来和 PyIntObject 大战一场。我们渴望在运行时观察 Python 的整数对象体系的变化。这一点,完全可以通过修改 Python 源码来实现。我们修改了 int_print 的行为,让它打印出关于 block_list 和 free_list 的信息,以及小整数缓冲池的信息:
- static int int_print(PyIntObject *v, FILE *fp, int flags)
- {
- PyIntObject* intObjectPtr;
- PyIntBlock *p = block_list;
- PyIntBlock *last = NULL;
- int count = 0;
- int i;
- while(p != NULL)
- {
- ++count;
- last = p;
- p = p->next;
- }
- intObjectPtr = last->objects;
- intObjectPtr += N_INTOBJECTS - 1;
- printf("address @%p\n", v);
- printf("********** value\trefCount **********\n");
- for(i = 0; i < 10; ++i, --intObjectPtr)
- {
- printf("%d\t\t%d\n", intObjectPtr->ob_ival, intObjectPtr->ob_refcnt);
- }
- printf("block_list count : %d\n", count);
- printf("free_list : %p\n\n", free_list);
- return 0;
- }
需要特别注意的是,在初始化小整数缓冲池时,对于 block_list 以及每个 PyIntBlock 的 objects,都是从后往前开始填充的,所以在初始化完成后,-5 应该在最后一个 PyIntBlock 对象的 objects 内最后一块内存,所以我们需要顺藤摸瓜,一直找到这最后的一块内存,才能观察从 -5 到 4 这 10 个小整数。
首先我们创建一个 PyIntObject 对象 -9999,从 图10 所示的输出信息可以看到,小整数对象很多都被 Python 自身使用多次了。
现在的free_list指向地址为00B6AF9C的内存,根据上面对PyIntObject的分析,那么下一个PyIntObject会在这个地址安身立命。那么好,我们接着再建立了两个PyIntObject对象,它们的值分别是-123456:
从图11所示的结果中可以看到a的地址正是创建i后free_list所指的地址,而b的地址也正是创建a后free_list所指的地址。虽然a和b的值都是一样的,但是它们确实是两个完全没有关系的PyIntObject对象,这点从地址上看得一清二楚。
现在我们将b删除,结果如图12所示:
删除b后,free_list回退到了a创建后free_list的位置,这一点也跟前面的分析是一致的。
最后我们来看一看对小整数对象的监控,连续两次创建PyIntObject对象-5,结果如图13所示:
可以看到,两次创建的 PyIntObject 对象 d1 和 d2 的地址都是一样的,这证明它们实际上是同一个对象。同时我们看到小整数池中-5的引用计数发生了变化,这证明d1和d2实际上都是指向这个对象。此外,free_list 没有发生任何变化。这些都与我们对 PyIntObject 的分析相符。