Skip to main content

Include/internal/pycore_gc.h

Source:

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_gc.h

pycore_gc.h defines the internal structures used by the cyclic garbage collector. Every container object (list, dict, tuple, etc.) has a PyGC_Head prepended to its allocation.

Map

LinesSymbolRole
1-50PyGC_HeadDoubly-linked list node prepended to every tracked object
51-100_PyGC_BITS_* macrosBit flags stored in _gc_prev low bits
101-130Generation structgc_list, count, threshold for each of 3 generations
131-160_PyObject_GC_IS_TRACKEDFast check: is this object currently in a GC list?
161-180_Py_AS_GCConvert PyObject *PyGC_Head *

Reading

PyGC_Head

// CPython: Include/internal/pycore_gc.h:12 PyGC_Head
typedef struct {
uintptr_t _gc_next; /* next object in the GC doubly-linked list */
uintptr_t _gc_prev; /* prev object + bit flags in low 2 bits */
} PyGC_Head;

The GC list is embedded directly in the object's allocation, one PyGC_Head before ob_refcnt. This avoids a separate allocation for list membership.

Bit flags in _gc_prev

// CPython: Include/internal/pycore_gc.h:58 _PyGC_BITS
#define _PyGC_BITS_FINALIZED (1) /* tp_finalize already called */
#define _PyGC_BITS_UNREACHABLE (2) /* temporary: in unreachable list during collection */
/* Low 2 bits are flags; high bits are the actual prev pointer (aligned) */
#define _PyGCHead_PREV(g) ((PyGC_Head *)((g)->_gc_prev & ~3))

Because PyGC_Head is aligned to at least 4 bytes, the two low bits of _gc_prev are always zero and can be used as flags.

Generation structure

// CPython: Include/internal/pycore_gc.h:110 _PyGC_generation
struct gc_generation {
PyGC_Head head; /* sentinel node for the doubly-linked list */
int threshold; /* collection trigger: collect when count >= threshold */
int count; /* number of objects or collections since last collection */
};

/* Three generations, indexed 0 (young) to 2 (old) */
struct _gc_runtime_state {
struct gc_generation generations[NUM_GENERATIONS];
...
};

_Py_AS_GC

// CPython: Include/internal/pycore_gc.h:165 _Py_AS_GC
#define _Py_AS_GC(o) ((PyGC_Head *)(o) - 1)
#define _Py_FROM_GC(g) ((PyObject *)((PyGC_Head *)(g) + 1))

PyObject * to PyGC_Head * is just pointer arithmetic: the GC header sits immediately before the object.

Tracking check

// CPython: Include/internal/pycore_gc.h:145 _PyObject_GC_IS_TRACKED
#define _PyObject_GC_IS_TRACKED(o) \
(_Py_AS_GC(o)->_gc_next != 0)

An object is tracked (in a GC generation list) when _gc_next != 0. Untracked objects have _gc_next == 0 — the GC ignores them.

gopy notes

In gopy the GC is Go's own concurrent mark-and-sweep GC; cyclic garbage is handled automatically. PyGC_Head has no direct equivalent. _PyObject_GC_IS_TRACKED maps to a boolean on objects.Object set when the object contains mutable references. Generation counts and thresholds are exposed via gc.get_threshold() / gc.set_threshold() using stored constants that match CPython defaults (700/10/10).