Skip to main content

Objects/memoryobject.c

cpython 3.14 @ ab2d84fe1023/Objects/memoryobject.c

memoryobject.c implements CPython's memoryview type. A PyMemoryViewObject wraps a Py_buffer descriptor (pointer to the underlying memory, format string, shape, strides, suboffsets, and an export count). The buffer protocol lets numpy arrays, bytes, bytearray, and ctypes objects share memory without copying. memoryview is the user-visible handle into that shared region.

Map

LinesSymbolRolegopy
1-60includes, MV_C_CONTIGUOUS macrosHeader and contiguity flag helpersnot ported
61-180PyBUF_* flag validation, init_strides_from_shapeBuffer layout initialisationnot ported
181-320memory_alloc, mbuf_alloc, mbuf_add_viewAllocation of the managed-buffer layernot ported
321-480PyMemoryView_FromObject, PyMemoryView_FromBufferPublic constructors; call bf_getbuffer on the targetnot ported
481-620memory_getbuffer, memory_releasebufferImplement bf_getbuffer/bf_releasebuffer for nested viewsnot ported
621-760copy_single, copy_recElement-wise copy respecting strides and suboffsetsnot ported
761-900pack_single, unpack_singleRead/write one typed element using the format codenot ported
901-1040memoryview_getitem, memoryview_setitemsq_item/sq_ass_item slots; index into a flat or multi-dimensional viewnot ported
1041-1200memoryview_subscript, memoryview_ass_subscriptSlice and multi-index support via mp_subscriptnot ported
1201-1380memory_richcompareElement-by-element equality comparison between two viewsnot ported
1381-1520memory_hashCompute hash over the contiguous byte representationnot ported
1521-1660memory_tobytes, memory_tolist, memory_toreadonlyConversion methodsnot ported
1661-1800memory_castReinterpret the buffer under a new format/shapenot ported
1801-1940memory_repr, memory_getattroRepr and attribute access for shape/strides/format propertiesnot ported
1941-2060memory_enter, memory_exitContext manager protocol; __exit__ calls memory_releasenot ported
2061-2200PyMemoryView_Type definitionFull PyTypeObject with buffer, sequence, and mapping slotsnot ported

Reading

Buffer acquisition and the managed-buffer layer (lines 181 to 480)

cpython 3.14 @ ab2d84fe1023/Objects/memoryobject.c#L181-480

CPython introduces a two-level structure to handle nested views safely. A "managed buffer" (_PyManagedBufferObject) is an internal reference-counted wrapper that holds the root Py_buffer and an export count. Every memoryview that points into the same root object shares one managed buffer:

static _PyManagedBufferObject *
mbuf_alloc(void)
{
_PyManagedBufferObject *mbuf;
mbuf = (_PyManagedBufferObject *)
PyObject_GC_New(_PyManagedBufferObject, &_PyManagedBuffer_Type);
if (mbuf == NULL)
return NULL;
mbuf->flags = 0;
mbuf->exports = 0;
memset(&mbuf->master, 0, sizeof(Py_buffer));
mbuf->master.obj = NULL;
return mbuf;
}

PyMemoryView_FromObject calls bf_getbuffer on the source once and stores the result in a new managed buffer. Slicing that view creates a second PyMemoryViewObject sharing the same managed buffer, so the underlying bf_releasebuffer is called exactly once when all views are released.

Element access via format codes (lines 761 to 1040)

cpython 3.14 @ ab2d84fe1023/Objects/memoryobject.c#L761-1040

pack_single and unpack_single implement a mini interpreter over the buffer's format string. Each letter maps to a C type; for example 'd' reads or writes a double, 'i' a signed 32-bit integer. memoryview_getitem uses the stride array to compute the byte offset for a linear index and then delegates to unpack_single:

static PyObject *
memoryview_getitem(PyMemoryViewObject *self, Py_ssize_t index)
{
Py_buffer *view = &self->view;
char *ptr;

CHECK_RELEASED(self);
if (view->ndim != 1) {
PyErr_SetString(PyExc_NotImplementedError,
"multi-dimensional sub-views are not implemented");
return NULL;
}
ptr = (char *)view->buf + view->strides[0] * index;
return unpack_single(self, ptr, view->format);
}

Strides allow the view to describe non-contiguous memory such as a column slice of a 2-D array, without moving any data.

memory_cast and format reinterpretation (lines 1661 to 1800)

cpython 3.14 @ ab2d84fe1023/Objects/memoryobject.c#L1661-1800

memoryview.cast(format) is one of the more complex methods. It validates that the total byte length is divisible by the new element size, builds a fresh shape tuple, and returns a new PyMemoryViewObject pointing into the same managed buffer with a different format and strides:

static PyObject *
memory_cast(PyMemoryViewObject *self, PyObject *args, PyObject *kwds)
{
...
/* new view shares the same managed buffer */
mv = (PyMemoryViewObject *)
mbuf_add_incomplete_view(self->mbuf, NULL, ndim == 0 ? 1 : ndim);
if (mv == NULL)
goto error;
mv->view.format = PyBytes_AS_STRING(format_as_bytes);
mv->view.itemsize = itemsize;
...
}

Because the managed buffer's export count is incremented, the original source object stays alive as long as any view, regardless of format, references it.

gopy mirror

memoryobject.c has no counterpart in gopy yet. A future port would live in objects/memoryview.go. The buffer protocol itself (the bf_getbuffer / bf_releasebuffer pair) would need to be expressed as a Go interface, likely BufferProtocol, that bytes, bytearray, and array objects implement. The managed-buffer two-level structure maps naturally to a Go struct with a reference count protected by a mutex.

CPython 3.14 changes

CPython 3.14 added memoryview.toreadonly() as a stable API (the method existed in 3.12 but was not in the stable ABI). The memory_cast path gained stricter validation rejecting casts that would create views with suboffsets, closing a potential out-of-bounds access. The Py_buffer.obj ownership rules were clarified in the documentation but the C code itself is unchanged in structure.