Reputation: 21
In the write-up The Lost Art of Structure Packing, the author introduces struct foo6 (...)
in chapter 6 :
struct foo6 {
short s;
char c;
int flip:1;
int nybble:4;
int septet:7;
};
They explain that the padding is done as followed :
struct foo6 {
short s; /* 2 bytes */
char c; /* 1 byte */
int flip:1; /* total 1 bit */
int nybble:4; /* total 5 bits */
int pad1:3; /* pad to an 8-bit boundary */
int septet:7; /* 7 bits */
int pad2:25; /* pad to 32 bits */
};
But I don't get why the last bit field is padded to 32 bits. Following the previous explanations, I would have guessed a padding to 16 bits, because the strictest alignment condition is 2 bytes, for the short element. By padding 9 bits to the septet, the struct can be properly aligned on even addresses.
I have a feeling it has to do with the fact that it is a 32 bits system (as in sizeof int
== 4) but it is not consistent with the previous explanations.
I have compiled printed the sizeof (struct foo6)
with -m32 and -m64, and the result is indeed 8, not 6. But I still don't understand why.
Upvotes: 2
Views: 349
Reputation: 12708
Structs always need to be padded to fill the worst case of alignment of any of the fields used in the structure. This is what enforces the padding at the end of the structure, and can make one to think that some bytes are wasted at the end. The reason of this is alignment.
Imagine that a structure makes padding of fields to make one of the fields to be aligned to a 32bit boundary, and finally the struct
itself requires 27 bytes. If you are to use it in an array, and don't pad it with one extra byte, the first element of the array will be aligned, but the second will be put at an address that is congruent to 3 mod 4, and every multiple of 4 bytes field will be misaligned one byte of the structure. And for this reason the compiler will add an extra byte at the end of the structure for padding.
Once said this, the alignment of any composed data type, will be the same as the one used in the substructure member with the biggest alignment requirements. In the structure fields, the padding is required to align the next field correctly, and at the end, the padding is required to make the full data type aligned with the next entry in an array of the data type you are defining. In this way you can compute padding and alignment for any compound data type, to be able to join this new created data type with any other in a new data structure, or an array of this new data type.
To compute this, the compiler saves in its type table two sizes, the element size, and the type alignment. These quantities don't have to be equal, for example on a 32 bit machine a short
is 2 bytes width, and it will normally have a 2 byte address alignment requirement, but it is possible that it can be accessed in one bus access if positioned at an address that is congruent to 1 mod 4, because it will fit inside a full word, and so have no penalty to be read in a single bus access (while this complicates calculations a lot, I will not enter on this case which is seldom used) So the algorithm normally requires to check the computed offset of a field, and on adding its size, how the alignment is to fit the next field, and add the needed padding bytes to enforce the alignment of the next field. So in the first pass, we will start computing the sizes and alignment requirements of all the fields in the list of fields, grouping the bitfields into the base type chunks (so the int
size will be needed to adapt the bitfields into separate padding groups) This way, we have:
char
fields: These have size 1 and no alignment (consider alignment 1, so they can be aligned at any address)short
fields: These have size 2 and alignment to even addresses (2).int
fields: We'll consider a 32bit machine so they have 4 size and 4 alignment.long
fields: 8 size, and 4 alignment (in a 32 bit machine, 8 alignment in a 64bit)float
fields: 4 size and 4 alignmentdouble
fields: 8 size and 4 alignment.Now assume we try to pad the struct foo1
data type:
struct
and update the alignment if the alignment of this field is larger than the previously calculated.If we apply this to struct foo1
it will result in:
struct foo6 {
short s; /* two bytes size, two bytes alignment */
char c; /* one byte size, one byte alignment */
int flip:1; /* next three fields fit in one 32bit int so
* 4 bytes size, 4 bytes alignment. */
int nybble:4;
int septet:7;
};
struct foo1
size: 8, alignment: 4.In the case of struct foo2
:
struct foo6 {
short s; /* 2 bytes, alignment 2 */
char c; /* 1 byte, alignment 1 */
int flip:1; /* group 1: flip, nybble and septet */
int nybble:4; /* total 12 */
int septet:7; /* size 4, alignment 4 */
int pad2:25; /* size 4, alignment 4 */
};
s
: offset 0, size 2, alignment 2, total size 2, total alignment 2.c
: offset 2, size 1, alignment 1, total size 3, total alignment 2.Upvotes: 0
Reputation: 67835
struct foo6 {
short s;
char c;
int flip:1;
int nybble:4;
int septet:7;
};
struct foo7 {
short s; /* 2 bytes */
char c; /* 1 byte */
int flip:1; /* total 1 bit */
int nybble:4; /* total 5 bits */
int pad1:3; /* pad to an 8-bit boundary */
int septet:7; /* 7 bits */
int pad2:25; /* pad to 32 bits */
};
I have compiled printed the sizeof (struct foo6) with -m32 and -m64, and the result is indeed 8, not 6. But I still don't understand why.
First of all - it is hard to understand which struct as you use the same names to make it unclear. Assuming not packing
foo6
short [][]
char []
padding []
bitfields [][][]
padding []
total 8 bytes
foo7
short [][]
char []
bitfields [] <-- GCC will pack them into char
bitfields [][][][]
total 8 bytes
Upvotes: 1