Skip to content

Memory Management

Overview

Python manages memory through a private heap that contains all Python objects and data structures. The Python memory manager handles sharing, segmentation, preallocation, and caching internally.

  • Raw Memory Allocator: Interfaces directly with the operating system to ensure sufficient space in the private heap.
  • Object-specific Allocators: Operate on top of the raw allocator, implementing policies tailored to specific object types (e.g., integers vs. dictionaries).
  • Internal Control: The interpreter manages the heap automatically. Extension writers should use the provided C API functions rather than standard C library functions to ensure the interpreter accurately tracks its memory footprint.

The Three Allocation Domains

Every allocation function in the Python C API belongs to one of three domains. You must use the same domain for both allocation and deallocation of a specific block.

Domain Purpose Thread State Required? Primary Functions
Raw General-purpose buffers; system-level allocation. No PyMem_RawMalloc, PyMem_RawFree
Mem Internal Python buffers and general-purpose heap memory. Yes PyMem_Malloc, PyMem_Free
Object Allocation specifically for Python objects. Yes PyObject_Malloc, PyObject_Free

Note for Free-Threaded Builds: In free-threaded versions of Python, it is a hard requirement that Python objects are allocated exclusively using the Object domain. General buffers must use the Raw or Mem domains.


API Reference

Raw Memory Interface

These functions are wrappers for the system allocator and are safe to call without holding the Global Interpreter Lock (GIL) or having an attached thread state.

  • void *PyMem_RawMalloc(size_t n): Allocates n bytes. Returns NULL on failure.
  • void *PyMem_RawCalloc(size_t nelem, size_t elsize): Allocates memory for an array and initializes it to zeros.
  • void *PyMem_RawRealloc(void *p, size_t n): Resizes a previously allocated block.
  • void PyMem_RawFree(void *p): Releases memory allocated via the Raw domain.

Memory Interface (Standard)

These functions use the pymalloc allocator by default. They require an attached thread state.

  • void *PyMem_Malloc(size_t n): Allocates n bytes from the Python heap.
  • void *PyMem_Free(void *p): Frees memory allocated via PyMem_Malloc or PyMem_Calloc.

Type-Oriented Macros:

  • PyMem_New(TYPE, n): Allocates (n * sizeof(TYPE)) bytes and casts to TYPE*.
  • PyMem_Resize(p, TYPE, n): Resizes the block to n elements of TYPE.
  • PyMem_Del(p): Alias for PyMem_Free.

Object Allocator

Optimized for small allocations (typically ≤ 512 bytes) using the pymalloc algorithm.

  • void *PyObject_Malloc(size_t n): Essential for creating new Python object types.
  • void PyObject_Free(void *p): Must be used for any pointer returned by PyObject_Malloc.

Safety & Best Practices

The Cardinal Rule: Do Not Mix Allocators

Never mix standard C library functions (malloc, free) with Python memory manager functions for the same memory block.

  • Wrong: ptr = malloc(10); PyMem_Free(ptr);Crash/Corruption
  • Correct: ptr = PyMem_Malloc(10); PyMem_Free(ptr);

Error Handling

Always check for NULL returns. If an allocation fails, you should typically return a Python exception.

char *buf = PyMem_New(char, 1024);
if (buf == NULL) {
    return PyErr_NoMemory();
}

Why use Python's Heap instead of malloc?

  1. Performance: pymalloc is often faster than the system malloc for small, frequent allocations.
  2. Garbage Collection: Using the Mem/Object domains allows the interpreter to track the memory footprint, which can trigger garbage collection when memory pressure is high.
  3. Debugging: Python's debug hooks (enabled via PYTHONMALLOC) can detect buffer overflows and memory leaks more effectively when using the official API.

Implementation Example

PyObject *example_function(PyObject *self, PyObject *args) {
    PyObject *res;
    // Allocate a buffer using the Python Mem domain
    char *buf = PyMem_New(char, 512); 

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

    // Perform operations...
    res = PyBytes_FromString(buf);

    // Clean up using the matching domain function
    PyMem_Free(buf); 
    return res;
}