Is a C struct of all-packed fields except first one different from packed struct?

73 views Asked by At

Among its Common Type Attributes, GCC provides packed:

This attribute, attached to a struct [...] type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. This is equivalent to specifying the packed attribute on each of the members.

Furthermore, from the C18 Standard (ยง 6.7.2.1, 15-17):

Within a structure object, the non-bit-field members [...] have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member [...], and vice versa. There may be unnamed padding within a structure object, but not at its beginning. [...] There may be unnamed padding at the end of a structure [...].

So, given that there is no padding at the beginning of a structure, using packed on its first member seems redundant. Reciprocally (and this is where my issue lies), using packed on all of its members apart from the first one seems unnecessarily complicated and equivalent to using packed on the structure itself (we make the assumption that all members are of integer type(s)).

However I have come multiple times across code similar to:

struct S {
    unsigned a;
    unsigned b __attribute__ ((__packed__));
    unsigned c __attribute__ ((__packed__));
    unsigned d __attribute__ ((__packed__));
};

and I am unable to figure out why the author preferred such a struggle over

struct S {
    unsigned a;
    unsigned b;
    unsigned c;
    unsigned d;
} __attribute__ ((__packed__));

Is it a matter of alignment of the structure itself that packed on the first member would alter? Is it coming from something other than padding and alignment that I haven't thought about (e.g. quirk in older versions of GCC)? On the other hand, if they are equivalent, how can I be sure of it, given that the documentation is IMHO of little help here?

Although it is not a valid proof that they are equivalent, I have tried compiling both structs for various architectures (x86, x86_64, Aarch64) and they always seemed to share the same layout.

1

There are 1 answers

3
Eric Postpischil On BEST ANSWER

If the first member is not packed, it still has its original alignment requirement, which may be greater than one byte, and therefore the structure has at least that alignment requirement, and this may also require padding at the end of the structure.

For:

struct S {
    unsigned a;
    unsigned b __attribute__ ((__packed__));
    unsigned c __attribute__ ((__packed__));
    unsigned d __attribute__ ((__packed__));
};

GCC 10.2 for x86-64 says _Alignof(struct S) is 4. Whereas, if a or the whole structure is marked __attribute__ ((__packed__));, it says the alignment is 1.

If we change the last member to char:

struct S {
    unsigned a;
    unsigned b __attribute__ ((__packed__));
    unsigned c __attribute__ ((__packed__));
    char d __attribute__ ((__packed__));
};

then GCC says its size is 16 bytes, showing it has added three bytes of padding. If either a or the whole structure is marked __attribute__ ((__packed__)), the size changes to 13.