Mismatch between sizeof in ctypes.struct (packed) and packed struct in C Mismatch between sizeof in ctypes.struct (packed) and packed struct in C python-3.x python-3.x

Mismatch between sizeof in ctypes.struct (packed) and packed struct in C


The ctypes docs https://docs.python.org/3/library/ctypes.html#structure-union-alignment-and-byte-order state clearly that

By default, Structure and Union fields are aligned in the same way theC compiler does it. It is possible to override this behavior byspecifying a pack class attribute in the subclass definition. Thismust be set to a positive integer and specifies the maximum alignmentfor the fields. This is what #pragma pack(n) also does in MSVC.

This means they don't refer to what gcc does but to the MSVC #pragma pack, instead. See: https://docs.microsoft.com/en-us/cpp/preprocessor/pack?view=vs-2019

n

(Optional) Specifies the value, in bytes, to be used for packing. Ifthe compiler option /Zp isn't set for the module, the default valuefor n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of amember is on a boundary that's either a multiple of n, or a multipleof the size of the member, whichever is smaller.

So, one reason for the observed behavior lies in the compiler specifics of MSVC vs. gcc.

The gcc docs https://gcc.gnu.org/onlinedocs/gcc-4.9.4/gcc/Type-Attributes.html#Type-Attributes (Sorry, docs for gcc-4.9.2 were not available on the gcc page), on the other hand cleary tells that:

This attribute(packed), attached to struct or union type definition, specifiesthat each member (other than zero-width bit-fields) of the structureor union is placed to minimize the memory required. When attached toan enum definition, it indicates that the smallest integral typeshould be used.

They do not tell nothing about boundary requirements, whereas the memory footprint is minimized, in this case. If it makes sense to place a bit field across a byte boundary may be topic of another discussion.It appears quite logical and consistent to me why __attribute__((aligned(1))) actually setups the struct as expected, because it explicitely enforces byte alignment of the struct members, which does not seem to be the case by specifying __attribute__((packed)). In the first case, the gcc compiler consequently enforces byte alignment for bit fields, too.

To summarize:

  • MSVC #pragma pack(n) specifies the alignment of the struct members to be placed at memory offsets being multiples of n
  • gcc's __attribute__ ((aligned (n))) asks for alignment of struct (members)
  • gcc's __attribute__ ((__packed__))asks for minimizing memory footprint, even if that means that bitfields byte boundaries are crossed
  • Combinations of gcc's packed and aligned attributes also asks for reduction of memory footprint but with the restriction of enforcing the alignment, too. So bitfield byte boundaries are not crossed


I don't think alignment is the issue here and hence setting the alignment to 1 in python has not the desired effect.

The problem is that bitfields in C fill up the given type but not cross the border. An uint32_t has 32bit and each member needs 20bit. So each member will be put into a separate uint32_t for a total of 8 byte.

Somehow the packed attribute causes gcc to ignore that rule and use 40bit total.

Python ctypes on the other hand follows that rule and gets 8.