[绝对原创 转载请注明出处]
Python源码剖析
——整数对象PyIntObject(3)
本文作者: Robert Chen (search.pythoner@gmail.com)
3 Hack PyIntObject
现在,让我们荡起双桨,哦不对,让我们挽起衣袖和裤脚J,来和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的分析相符。