Thursday, 3 March 2016

Structure alignment and padding

In general, a struct instance will have the alignment of its widest scalar member. Compilers do this as the easiest way to ensure that all the members are self-aligned for fast access.
Also, in C the address of a struct is the same as the address of its first member - there is no leading padding. Beware: in C++, classes that look like structs may break this rule! (Whether they do or not depends on how base classes and virtual member functions are implemented, and varies by compiler.)
(When you’re in doubt about this sort of thing, ANSI C provides an offsetof() macro which can be used to read out structure member offsets.)
Consider this struct:
struct foo1 {
char *p;
long x;
char c;
};
Assuming a 64-bit machine, any instance of struct foo1 will have 8-byte alignment. The memory layout of one of these looks unsurprising, like this:
struct foo1 {
char *p; /* 8 bytes */
char c; /* 1 byte
long x; /* 8 bytes */
char pad[7]; /* 7 bytes */
};
It’s laid out exactly as though variables of these types has been separately declared. But if we put c first, that’s no longer true.
struct foo2 {
char c; /* 1 byte */
char pad[7]; /* 7 bytes */
char *p; /* 8 bytes */
};
long x; /* 8 bytes */
If the members were separate variables, c could start at any byte boundary and the size of pad might vary. Because struct foo2 has the pointer alignment of its widest member, that’s no longer possible. Now c has to be pointer-aligned, and following padding of 7 bytes is locked in.
Now let’s talk about trailing padding on structures. To explain this, I need to introduce a basic concept which I’ll call the stride address of a structure. It is the first address following the structure data that has the same alignment as the structure.
The general rule of trailing structure padding is this: the compiler will behave as though the structure has trailing padding out to its stride address. This rule controls what sizeof() will return.
Consider this example on a 64-bit x86 or ARM machine:
struct foo3 {
char *p; /* 8 bytes */
char c; /* 1 byte */
struct foo3 quad[4];
};
struct foo3 singleton;
You might think that sizeof(struct foo3) should be 9, but it’s actually 16. The stride address is that of (&p)[2]. Thus, in the quad array, each member has 7 bytes of trailing padding, because the first member of each following struct wants to be self-aligned on an 8-byte boundary. The memory layout is as though the structure had been declared like this:
struct foo3 {
char *p; /* 8 bytes */
char c; /* 1 byte */
};
char pad[7];
For contrast, consider the following example:
struct foo4 {
short s; /* 2 bytes */
char c; /* 1 byte */
};
Because s only needs to be 2-byte aligned, the stride address is just one byte after c, and struct foo4 as a whole only needs one byte of trailing padding. It will be laid out like this:
struct foo4 {
short s; /* 2 bytes */
char c; /* 1 byte */
};
char pad[1];
and sizeof(struct foo4) will return 4.
Here’s a last important detail: If your structure has structure members, the inner structs want to have the alignment of longest scalar too. Suppose you write this:
struct foo5 {
char c;
struct foo5_inner {
char *p; short x;
};
} inner;
The char *p member in the inner struct forces the outer struct to be pointer-aligned as well as the inner. Actual layout will be like this on a 64-bit machine:
struct foo5 {
char c; /* 1 byte*/
char pad1[7]; /* 7 bytes */
struct foo5_inner {
short x; /* 2 bytes */
char *p; /* 8 bytes */
};
char pad2[6]; /* 6 bytes */
} inner;
This structure gives us a hint of the savings that might be possible from repacking structures. Of 24 bytes, 13 of them are padding. That’s more than 50% waste space!

No comments:

Post a Comment