Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9d4be49
Restore max field size to sys.maxsize, as in Python 3.13 & below
encukou Dec 18, 2024
09c81a8
PyCField: Split out bit/byte sizes/offsets.
encukou Jan 9, 2025
c397cf4
Expose CField
encukou Jan 10, 2025
20ecd84
Add generic checks for all the test structs/unions
encukou Jan 17, 2025
60e7b32
More testing
encukou Jan 17, 2025
18334d8
Tests: import CField from ctypes
encukou Jan 17, 2025
294b1b8
Clarify bit_offset
encukou Jan 17, 2025
52114fa
Add a blurb
encukou Jan 17, 2025
32d41f3
Regen
encukou Jan 17, 2025
f6f596f
Merge in the main branch
encukou Jan 17, 2025
9b0e7eb
include <stdbool.h> in the common header
encukou Jan 17, 2025
d7bd835
Explicit casts
encukou Jan 17, 2025
06458f6
Remove problematic assert
encukou Jan 17, 2025
bb41481
Merge in the main branch
encukou Jan 24, 2025
91365b0
Add a test for the new info, and fix 'name' for nested anonymous structs
encukou Jan 24, 2025
b6d0510
Use subTest
encukou Jan 24, 2025
6e279e2
Use PyUnicode_FromObject to get an exact PyUnicode
encukou Jan 27, 2025
176de87
Normalize exception message
encukou Jan 31, 2025
085720e
Fix refcounting
encukou Jan 31, 2025
05c9591
Add pretty spaces
encukou Jan 31, 2025
b77074c
Merge in the main branch
encukou Jan 31, 2025
4e95755
Fix bit-packed size test for big-endian machines
encukou Jan 31, 2025
9cde20e
Remove `size` from _layout.py
encukou Jan 31, 2025
34865e8
Fix bit_offset for big-endian structs (where bitfields are laid out f…
encukou Feb 7, 2025
89fc44d
Merge in the main branch
encukou Feb 7, 2025
f327ffb
Name the magic constant
encukou Feb 7, 2025
14270ad
Remove unused variable
encukou Feb 7, 2025
7ce3cb9
Remove an unacceptable blank line
encukou Feb 7, 2025
d9d593c
Update documentation for tp_basicsize & tp_itemsize
encukou Feb 8, 2025
d52b8c9
Merge in the main branch
encukou Feb 21, 2025
2cdcb5b
Skip "is in" test for bitfields of underaligned types (bug filed)
encukou Feb 21, 2025
753090a
Merge in the main branch
encukou Mar 14, 2025
fde4204
Address review
encukou Mar 14, 2025
b2fddd8
One more alignment
encukou Mar 14, 2025
5ce595c
Don't use `self` while it's NULL
encukou Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/c-api/allocation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Allocating Objects on the Heap
The size of the memory allocation is determined from the
:c:member:`~PyTypeObject.tp_basicsize` field of the type object.

Note that this function is unsuitable if *typeobj* has
:c:macro:`Py_TPFLAGS_HAVE_GC` set. For such objects,
use :c:func:`PyObject_GC_New` instead.


.. c:macro:: PyObject_NewVar(TYPE, typeobj, size)

Expand All @@ -49,6 +53,10 @@ Allocating Objects on the Heap
fields into the same allocation decreases the number of allocations,
improving the memory management efficiency.

Note that this function is unsuitable if *typeobj* has
:c:macro:`Py_TPFLAGS_HAVE_GC` set. For such objects,
use :c:func:`PyObject_GC_NewVar` instead.


.. c:function:: void PyObject_Del(void *op)

Expand Down
3 changes: 3 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ The following functions and structs are used to create
class need *in addition* to the superclass.
Use :c:func:`PyObject_GetTypeData` to get a pointer to subclass-specific
memory reserved this way.
For negative :c:member:`!basicsize`, Python will insert padding when
needed to meet :c:member:`~PyTypeObject.tp_basicsize`'s alignment
requirements.

.. versionchanged:: 3.12

Expand Down
96 changes: 65 additions & 31 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -587,47 +587,81 @@ and :c:data:`PyType_Type` effectively act as defaults.)


.. c:member:: Py_ssize_t PyTypeObject.tp_basicsize
Py_ssize_t PyTypeObject.tp_itemsize
Py_ssize_t PyTypeObject.tp_itemsize

These fields allow calculating the size in bytes of instances of the type.

There are two kinds of types: types with fixed-length instances have a zero
:c:member:`~PyTypeObject.tp_itemsize` field, types with variable-length instances have a non-zero
:c:member:`~PyTypeObject.tp_itemsize` field. For a type with fixed-length instances, all
instances have the same size, given in :c:member:`~PyTypeObject.tp_basicsize`.
:c:member:`!tp_itemsize` field, types with variable-length instances have a non-zero
:c:member:`!tp_itemsize` field. For a type with fixed-length instances, all
instances have the same size, given in :c:member:`!tp_basicsize`.
(Exceptions to this rule can be made using
:c:func:`PyUnstable_Object_GC_NewWithExtraData`.)

For a type with variable-length instances, the instances must have an
:c:member:`~PyVarObject.ob_size` field, and the instance size is :c:member:`~PyTypeObject.tp_basicsize` plus N
times :c:member:`~PyTypeObject.tp_itemsize`, where N is the "length" of the object. The value of
N is typically stored in the instance's :c:member:`~PyVarObject.ob_size` field. There are
exceptions: for example, ints use a negative :c:member:`~PyVarObject.ob_size` to indicate a
negative number, and N is ``abs(ob_size)`` there. Also, the presence of an
:c:member:`~PyVarObject.ob_size` field in the instance layout doesn't mean that the instance
structure is variable-length (for example, the structure for the list type has
fixed-length instances, yet those instances have a meaningful :c:member:`~PyVarObject.ob_size`
field).

The basic size includes the fields in the instance declared by the macro
:c:macro:`PyObject_HEAD` or :c:macro:`PyObject_VAR_HEAD` (whichever is used to
declare the instance struct) and this in turn includes the :c:member:`~PyObject._ob_prev` and
:c:member:`~PyObject._ob_next` fields if they are present. This means that the only correct
way to get an initializer for the :c:member:`~PyTypeObject.tp_basicsize` is to use the
:c:member:`~PyVarObject.ob_size` field, and the instance size is
:c:member:`!tp_basicsize` plus N times :c:member:`!tp_itemsize`,
where N is the "length" of the object.

Functions like :c:func:`PyObject_NewVar` will take the value of N as an
argument, and store in the instance's :c:member:`~PyVarObject.ob_size` field.
Note that the :c:member:`~PyVarObject.ob_size` field may later be used for
other purposes. For example, :py:type:`int` instances use the bits of
:c:member:`~PyVarObject.ob_size` in an implementation-defined
way; the underlying storage and its size should be acessed using
:c:func:`PyLong_Export`.

Also, the presence of an :c:member:`~PyVarObject.ob_size` field in the
instance layout doesn't mean that the instance structure is variable-length.
For example, the :py:type:`list` type has fixed-length instances, yet those
instances have a :c:member:`~PyVarObject.ob_size` field.
(As with :py:type:`int`, avoid reading lists' :c:member:`!ob_size` directly.
Call :c:func:`PyList_Size` instead.)

The :c:member:`!tp_basicsize` includes size needed for data of the type's
:c:member:`~PyTypeObject.tp_base`, plus any extra data needed
by each instance.

The correct way to set :c:member:`!tp_basicsize` is to use the
``sizeof`` operator on the struct used to declare the instance layout.
The basic size does not include the GC header size.

A note about alignment: if the variable items require a particular alignment,
this should be taken care of by the value of :c:member:`~PyTypeObject.tp_basicsize`. Example:
suppose a type implements an array of ``double``. :c:member:`~PyTypeObject.tp_itemsize` is
``sizeof(double)``. It is the programmer's responsibility that
:c:member:`~PyTypeObject.tp_basicsize` is a multiple of ``sizeof(double)`` (assuming this is the
alignment requirement for ``double``).

For any type with variable-length instances, this field must not be ``NULL``.
This struct must include the struct used to declare the base type.
In other words, :c:member:`!tp_basicsize` must be greater than or equal
to the base's :c:member:`!tp_basicsize`.

Since every type is a subtype of :py:type:`object`, this struct must
include :c:type:`PyObject` or :c:type:`PyVarObject` (depending on
whether :c:member:`~PyVarObject.ob_size` should be included). These are
usually defined by the macro :c:macro:`PyObject_HEAD` or
:c:macro:`PyObject_VAR_HEAD`, respectively.

The basic size does not include the GC header size, as that header is not
part of :c:macro:`PyObject_HEAD`.

For cases where struct used to declare the base type is unknown,
see :c:member:`PyType_Spec.basicsize` and :c:func:`PyType_FromMetaclass`.

Notes about alignment:

- :c:member:`!tp_basicsize` must be a multiple of ``_Alignof(PyObject)``.
When using ``sizeof`` on a ``struct`` that includes
:c:macro:`PyObject_HEAD`, as recommended, the compiler ensures this.
When not using a C ``struct``, or when using compiler
extensions like ``__attribute__((packed))``, it is up to you.
- If the variable items require a particular alignment,
:c:member:`!tp_basicsize` and :c:member:`!tp_itemsize` must each be a
multiple of that alignment.
For example, if a type's variable part stores a ``double``, it is
your responsibility that both fields are a multiple of
``_Alignof(double)``.

**Inheritance:**

These fields are inherited separately by subtypes. If the base type has a
non-zero :c:member:`~PyTypeObject.tp_itemsize`, it is generally not safe to set
These fields are inherited separately by subtypes.
(That is, if the field is set to zero, :c:func:`PyType_Ready` will copy
the value from the base type, indicating that the instances do not
need additional storage.)

If the base type has a non-zero :c:member:`~PyTypeObject.tp_itemsize`, it is generally not safe to set
:c:member:`~PyTypeObject.tp_itemsize` to a different non-zero value in a subtype (though this
depends on the implementation of the base type).

Expand Down
103 changes: 98 additions & 5 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -657,12 +657,13 @@ Nested structures can also be initialized in the constructor in several ways::
>>> r = RECT((1, 2), (3, 4))

Field :term:`descriptor`\s can be retrieved from the *class*, they are useful
for debugging because they can provide useful information::
for debugging because they can provide useful information.
See :class:`CField`::

>>> print(POINT.x)
<Field type=c_long, ofs=0, size=4>
>>> print(POINT.y)
<Field type=c_long, ofs=4, size=4>
>>> POINT.x
<ctypes.CField 'x' type=c_int, ofs=0, size=4>
>>> POINT.y
<ctypes.CField 'y' type=c_int, ofs=4, size=4>
>>>


Expand Down Expand Up @@ -2812,6 +2813,98 @@ fields, or any other data types containing pointer type fields.
present in :attr:`_fields_`.


.. class:: CField(*args, **kw)

Descriptor for fields of a :class:`Structure` and :class:`Union`.
For example::

>>> class Color(Structure):
... _fields_ = (
... ('red', c_uint8),
... ('green', c_uint8),
... ('blue', c_uint8),
... ('intense', c_bool, 1),
... ('blinking', c_bool, 1),
... )
...
>>> Color.red
<ctypes.CField 'red' type=c_ubyte, ofs=0, size=1>
>>> Color.green.type
<class 'ctypes.c_ubyte'>
>>> Color.blue.byte_offset
2
>>> Color.intense
<ctypes.CField 'intense' type=c_bool, ofs=3, bit_size=1, bit_offset=0>
>>> Color.blinking.bit_offset
1

All attributes are read-only.

:class:`!CField` objects are created via :attr:`~Structure._fields_`;
do not instantiate the class directly.

.. versionadded:: next

Previously, descriptors only had ``offset`` and ``size`` attributes
and a readable string representation; the :class:`!CField` class was not
available directly.

.. attribute:: name

Name of the field, as a string.

.. attribute:: type

Type of the field, as a :ref:`ctypes class <ctypes-data-types>`.

.. attribute:: offset
byte_offset

Offset of the field, in bytes.

For bitfields, this is the offset of the underlying byte-aligned
*storage unit*; see :attr:`~CField.bit_offset`.

.. attribute:: byte_size

Size of the field, in bytes.

For bitfields, this is the size of the underlying *storage unit*.
Typically, it has the same size as the bitfield's type.

.. attribute:: size

For non-bitfields, equivalent to :attr:`~CField.byte_size`.

For bitfields, this contains a backwards-compatible bit-packed
value that combines :attr:`~CField.bit_size` and
:attr:`~CField.bit_offset`.
Prefer using the explicit attributes instead.

.. attribute:: is_bitfield

True if this is a bitfield.

.. attribute:: bit_offset
bit_size

The location of a bitfield within its *storage unit*, that is, within
:attr:`~CField.byte_size` bytes of memory starting at
:attr:`~CField.byte_offset`.

To get the field's value, read the storage unit as an integer,
:ref:`shift left <shifting>` by :attr:`!bit_offset` and
take the :attr:`!bit_size` least significant bits.

For non-bitfields, :attr:`!bit_offset` is zero
and :attr:`!bit_size` is equal to ``byte_size * 8``.

.. attribute:: is_anonymous

True if this field is anonymous, that is, it contains nested sub-fields
that should be be merged into a containing structure or union.


.. _ctypes-arrays-pointers:

Arrays and pointers
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,11 @@ ctypes
to help match a non-default ABI.
(Contributed by Petr Viktorin in :gh:`97702`.)

* The class of :class:`~ctypes.Structure`/:class:`~ctypes.Union`
field descriptors is now available as :class:`~ctypes.CField`,
and has new attributes to aid debugging and introspection.
(Contributed by Petr Viktorin in :gh:`128715`.)

* On Windows, the :exc:`~ctypes.COMError` exception is now public.
(Contributed by Jun Komoda in :gh:`126686`.)

Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_get_sourcefile)
STRUCT_FOR_ID(_handle_fromlist)
STRUCT_FOR_ID(_initializing)
STRUCT_FOR_ID(_internal_use)
STRUCT_FOR_ID(_io)
STRUCT_FOR_ID(_is_text_encoding)
STRUCT_FOR_ID(_isatty_open_only)
Expand Down Expand Up @@ -295,6 +296,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(before)
STRUCT_FOR_ID(big)
STRUCT_FOR_ID(binary_form)
STRUCT_FOR_ID(bit_offset)
STRUCT_FOR_ID(bit_size)
STRUCT_FOR_ID(block)
STRUCT_FOR_ID(bound)
Expand All @@ -305,6 +307,8 @@ struct _Py_global_strings {
STRUCT_FOR_ID(buffers)
STRUCT_FOR_ID(bufsize)
STRUCT_FOR_ID(builtins)
STRUCT_FOR_ID(byte_offset)
STRUCT_FOR_ID(byte_size)
STRUCT_FOR_ID(byteorder)
STRUCT_FOR_ID(bytes)
STRUCT_FOR_ID(bytes_per_sep)
Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import ArgumentError
from _ctypes import SIZEOF_TIME_T
from _ctypes import CField

from struct import calcsize as _calcsize

Expand Down
Loading
Loading