Skip to main content

Modules/_ctypes/stgdict.c

cpython 3.14 @ ab2d84fe1023/Modules/_ctypes/stgdict.c

Modules/_ctypes/stgdict.c computes the StgInfo layout descriptor for composite ctypes types: Structure, Union, and Array. When a new Structure subclass is defined, its _fields_ sequence is processed here: field offsets and alignment are computed according to the target platform's ABI, an ffi_type struct is built recursively for libffi, and CField descriptors are attached to the class dict.

Map

LinesSymbolRole
1-60includes, StgInfo_FromObjectRetrieve StgInfo from a ctypes type
61-200MakeFieldsProcess _fields_ into CField descriptors
201-400StructUnionType_update_stginfoCompute offsets for Structure/Union
401-600PyCStructType_setattro_fields_ assignment triggers layout computation
601-750MakeAnonFieldsExpand anonymous nested fields into the parent namespace
751-900PyCArrayType_from_size_typeBuild Array type with computed total size

Reading

MakeFields: offset computation

MakeFields iterates _fields_ tuples and computes each field's byte offset. For Structure it accumulates offsets with alignment padding; for Union all offsets are zero and the total size is the maximum field size.

// CPython: Modules/_ctypes/stgdict.c:100 MakeFields
static int
MakeFields(PyObject *type, CFieldObject *fdesc, Py_ssize_t index, Py_ssize_t *pfield_size,
int *pbitofs, Py_ssize_t *psize, Py_ssize_t *pAlign, Py_ssize_t *plength)
{
...
stginfo->offset = field_size; /* current accumulated size becomes offset */
field_size += ffi_size;
if (ffi_size % align)
field_size += align - (ffi_size % align); /* alignment padding */

ffi_type recursive construction

StructUnionType_update_stginfo builds an ffi_type struct whose elements array contains pointers to the ffi_type of each field in order. This allows libffi to handle structs passed by value in function calls.

// CPython: Modules/_ctypes/stgdict.c:280 StructUnionType_update_stginfo
static int
StructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct)
{
...
ffi_type *fftype = PyMem_Malloc(sizeof(ffi_type));
fftype->type = FFI_TYPE_STRUCT;
fftype->elements = PyMem_Malloc((nFields+1) * sizeof(ffi_type *));
for (i = 0; i < nFields; i++)
fftype->elements[i] = field_stginfo->ffi_type_pointer;
fftype->elements[nFields] = NULL; /* sentinel */
stginfo->ffi_type_pointer = fftype;

Anonymous field expansion

When a _anonymous_ list is present, MakeAnonFields flattens the fields of nested anonymous structures into the parent namespace as if they were directly declared there.

// CPython: Modules/_ctypes/stgdict.c:660 MakeAnonFields
static int
MakeAnonFields(PyObject *type)
{
PyObject *anon_names = PyObject_GetAttrString(type, "_anonymous_");
...
for (i = 0; i < PySequence_Length(anon_names); i++) {
PyObject *fname = PySequence_GetItem(anon_names, i);
PyObject *fdesc = PyObject_GetAttr(type, fname);
/* copy each field of fdesc into type's dict with adjusted offset */

gopy notes

Not yet ported. ctypes structure layout is a low priority for gopy. When porting, the offset computation in MakeFields must handle both the default C ABI alignment and the _pack_ override for packed structures. Go's unsafe.Offsetof provides the equivalent for Go structs.

CPython 3.14 changes

3.14 moved StgInfo from a dict entry to an in-type C struct, eliminating the PyObject *stginfo dict lookup in MakeFields. The _fields_ validation now uses PySequence_Fast for better error messages on non-sequence inputs.