2.2.4. Padding#

In binary file formats, padding is often used to align data to certain byte boundaries. Caterpillar provides a way to handle padding within structs. However, it is important to note that caterpillar doesn’t store any data associated with the padding itself unless explicitly defined. If you need to retain or manipulate the padding content, you can use the Bytes or Memory field types.

If you want to apply padding to a struct, you can simply specify the padding length using the padding keyword. This is useful when you need to ensure that certain fields are aligned or when the structure requires reserved spaces.

>>> field = padding[10] # greedy or dynamic size

2.2.4.1. Whole-model alignment#

Added in version 2.10.0.

The padding field above reserves space explicitly, as an extra field of its own. Sometimes what you actually want is different: a whole @struct, @union or @bitfield class whose total packed size is always rounded up to a multiple of some alignment. Pass align_to= to the decorator instead:

>>> @struct(align_to=4)
... class Format:
...     a: uint8
...
>>> sizeof(Format)
4
>>> unpack(Format, b"\x01\x00\x00\x00")
Format(a=1)

This is deliberately different from Aligned and align(), which both pad relative to the absolute stream position. align_to= instead measures this model’s own start and end, so the padding only ever depends on the model’s own content - not on where it happens to be embedded:

>>> @struct
... class Outer:
...     b: uint8
...     inner: Format
...     c: uint8
...
>>> pack(Outer(b=1, inner=Format(a=2), c=3))
b'\x01\x02\x00\x00\x00\x03'

A bare int or context lambda is accepted directly, as shown above. Use AlignTo explicitly to customize the fill byte (or a multi-byte fill pattern) and/or relax verification:

>>> @struct(align_to=AlignTo(4, fill=0xFF, strict=False))
... class Lenient:
...     a: uint8

With the default strict=True, unpacking verifies that the padding bytes actually match fill and raises ValueError if they don’t. With strict=False, padding bytes are consumed without verification.

align_to= also works on @union (padding after the largest member) and @bitfield (padding after all bit-groups have been finalized to whole bytes), and applies per-element when the model is used inside an array, so each element keeps its own alignment:

>>> @struct
... class Many:
...     items: Format[2]
...
>>> pack(Many(items=[Format(a=1), Format(a=2)]))
b'\x01\x00\x00\x00\x02\x00\x00\x00'

A dynamic (context-dependent) alignment value is supported too, but makes the model’s size undeterminable ahead of time - sizeof() raises DynamicSizeError in that case.