: [Python-Dev] RE: Painful death in debug build

Tim Peters tim.one@home.com
Sat, 6 Oct 2001 04:35:01 -0400


[Tim]
> This is enough to reproduce the problem under a debug Windows build:
>
> ---------------------------------------------------------------
> class X(long):
>     pass
>
> x = X(0xffffL)
> print "before del"
> del x
> print "after del"
> ---------------------------------------------------------------
>
> It does not fail if the instance is created via
>
>     x = X(0x7fffL)
>
> instead.  It does fail on
>
>     x = X(0x8000L)
>
> The relevant difference is that Python uses 15-bit "digits" internally
> for longs, so 0x8000 may be the smallest value that will cause it to
> allocate more memory than already comes for free with the _longobject
> header

Now I'm in:

PyObject *
PyType_GenericAlloc(PyTypeObject *type, int nitems)
{
#define PTRSIZE (sizeof(PyObject *))

	int size;
	PyObject *obj;

	/* Inline PyObject_New() so we can zero the memory */

That comment doesn't seem to make much sense; the code following does a
whole bunch that PyObject_New() doesn't do.

	size = _PyObject_VAR_SIZE(type, nitems);

Then size = basicsize + 2 * itemsize = 26 + 2*2 = 30.

	/* Round up size, if necessary, so we fully zero out __dict__ */
	if (type->tp_itemsize % PTRSIZE != 0) {
		size += PTRSIZE - 1;
		size /= PTRSIZE;
		size *= PTRSIZE;
	}

I don't understand what that comment is trying to say; the code following it
bumps size up to 32, and that turns out to be "the problem":

	if (PyType_IS_GC(type)) {
		obj = _PyObject_GC_Malloc(type, nitems);
	}
	else {
		obj = PyObject_MALLOC(size);
	}

The subtype is a GC type, so _PyObject_GC_Malloc allocates

   sizeof(PyGC_Head) + (size_t)_PyObject_VAR_SIZE(tp, size) =
   12                + 30 =

42 bytes.  Note that the second term is the 30 originally computed for size,
not the 32 that size was later bumped up to.

	if (obj == NULL)
		return PyErr_NoMemory();

Nope.

	memset(obj, '\0', size);

Bingo!  obj is at offset 12 from the allocated block, and size is 32, so we
zero out the last 32 of the trailing 30 (=42-12) bytes allocated; i.e., we
zero out two bytes beyond the end of the allocated block.  Everything else
follows from that.  Seems pretty clear that this doesn't have anything
specific to do with longs.  Yup:

>>> class X(str):
...     pass
...
[6960 refs]
>>> x = X("ab")
[6969 refs]
>>> x = 1

That also blows up.

Since I'm not sure what the roundup of size is trying to accomplish, I'm not
sure what to do about it.  If I ignore the comment, I *imagine* we want
subtype pointer fields (like tp_dict) following the base-type prefix to be
naturally aligned for the architecture, and then padding makes sense to me.