In addition to eliminating padding in an aggregate, packing reduces the alignment requirement to one byte, as we see in this code:
#include <stdio.h>
int main(void)
{
{
union normal { int i; };
union packed { int i; } __attribute__((__packed__));
printf("Alignment of normal union is %zu.\n", _Alignof (union normal));
printf("Alignment of packed union is %zu.\n", _Alignof (union packed));
}
}
which outputs:
Alignment of normal union is 4.
Alignment of packed union is 1.
A corollary of this, as noted in Adrian Mole’s answer, is that a packed union does not require padding at its end to make its size a multiple of its alignment.
As I explained in this answer, although older documentation said the packed attribute was recursively applied to members, it was poorly phrased. The packed attribute merely reduces the alignment requirement of each member to one byte, so that it may be located at any address. It does not reach into members that are also aggregates and eliminate padding from them. Hence this code:
#include <stdio.h>
int main(void)
{
{
union normal { struct { char c; int i; }; };
union packed { struct { char c; int i; }; } __attribute__((__packed__));
printf("Size of normal union is %zu.\n", sizeof (union normal));
printf("Size of packed union is %zu.\n", sizeof (union packed));
}
}
outputs:
Size of normal union is 8.
Size of packed union is 8.