5

I'm trying to see why the size of struct differs when I move the struct variables around, I know there are padding involved but it's not apparent what it is doing in the background

struct test1 {
    long y;
    int a;
    short int b;
    short int t;
}

sizeof(struct test1) = 16

struct test2 {
    long y;
    short int b;
    int a;
    short int t;
}

sizeof(struct test2) = 24

struct test3 {
    int a;
    long y;
    short int b;
    short int t;
}

sizeof(struct test3) = 24

I get that the size of test1 is 8 + (4+2+2) with no padding, But I dont get why test2 does not return the same result, 8 + (2+4+2) with no padding.

The third test3 we see that int takes 4bytes + 4 padding, long takes 8 bytes, short int takes 2bytes + 2bytes + 4 padding.

If test3 can make two short int to become contiguous, why doesn't test2 make short int, int, and short int become contiguous?

Also, does this imply that we should always make sure to reorder struct members to minimize padding?

So test1 is ALWAYS better to declare compared to test2 and test3?

EDIT: As a follow up,

struct test4 {
    char asdf[3];
    short int b;
};

sizeof(struct test4) = 6

Shouldn't short int be padded to 4 bytes as char of array size 3 is padded to 4 bytes?

jimmyhuang0904
  • 181
  • 3
  • 10
  • 4
    General rule is always put your larger members first and smaller members last in decreasing order of size so the compiler doesn't add unnecessary padding to keep the members aligned. – David C. Rankin Dec 03 '19 at 06:32
  • 1
    @DavidRankin-ReinstateMonica: No, the general rule is to put the members with the strictest alignment requirements first. Putting larger members first will not help, and may hurt, when they are structures with low alignment requirements, arrays of `char`, and so on. – Eric Postpischil Dec 03 '19 at 11:25
  • 1
    You are nit-picking eric. By larger, that is what was meant. Not literally largest, `char buf[4000]` would be the smallest size of type while `uint64_t` would be larger. – David C. Rankin Dec 03 '19 at 14:29

3 Answers3

11

What's going on in the background is alignment. Alignment is the requirement that a data type has an address divisible by some unit. If that alignment unit is the size of the type itself, then that is the strictest alignment that exists in C conforming implementations.

C compilers tend to ensure certain alignment in struct layouts, even when the requirement doesn't come from the target hardware.

If we have a long that is, say, 4 bytes, followed by a two-byte short, that short can be placed immediately after the long, because the 4 byte offset is more than sufficiently alignend for a two byte type. The offset after those two members is then 6. But then your compiler doesn't consider 6 to be a suitable alignment for a 4 byte int; it wants a multiple of 4. Two bytes of padding is inserted to move that int to offset 8.

Of course, the actual numbers are compiler-specific. You have to know the sizes of your types and the alignment requirements and rules.

Also, does this imply that we should always make sure to reorder struct members to minimize padding?

If minimal structure size is important in your application, then you have to order the members from most strictly aligned to least strictly aligned. If minimal structure size isn't important, then you don't have to care about this.

Other concerns may weigh in, like compatibility with an externally imposed layout.

Or incremental growth. If a publicly used structure (referenced by numerous instances of compiled code such as executables and dynamic libraries) is maintained over time across multiple versions, typically new members must be added only at the end. In that case, we don't get the optimal order for minimum size, even if we would like that.

Shouldn't short int be padded to 4 bytes as char of array size 3 is padded to 4 bytes?

No, because the one byte of padding after the char [4] array brings the offset to 4. That offset is more than sufficiently aligned for the placement of a two-byte short. Moreover, no padding is required after that short. Why? The offset after the short is 6. The most strictly aligned member of the structure is that short, with an alignment requirement of 2. 6 is divisible by 2.

Here is a situation in which alignment would be required after the two-byte short: struct { long x; short y; }. Say long is 4 bytes. Or, let's make it 8, doesn't matter. If we place the 2 byte short after the 8 byte long, we have a size of 10. That causes a problem if we declare an array a of this structure, because a[1].x will be at offset 10 from the base of the array: x is misaligned. The most strictly aligned structure member is x, with an alignment requirement of (say) 8, same as its size. Thus, for the sake of array alignment, the structure must be padded for its size to be to be divisible by 8. Thus, 6 bytes of padding at the end will be required to bring the size to 16.

Basically padding before a member is for its own alignment, and padding at the end of a structure is to ensure that all members are aligned in an array, and that is driven by the most strictly aligned member.

Alignment is a hard hardware requirement on some platforms! If, say, a four byte data type is accessed at an address not divisible by four, a CPU exception occurs. On some such platforms, the CPU exception can be handled by the operating system, which implements the misaligned access in software rather than passing a potentially fatal signal to the process. That access is then very expensive, probably requiring on the order of a few hundred instructions. I seem to recall that in the MIPS port of Linux, this is a per-process option: handling misaligned exceptions can be turned on for some non-portable programs (e.g. ones developed for Intel x86) that depend on it, yet not turned on for programs which only perform a misaligned access due to some corruption bug (e.g. uninitialized pointer aimed at valid memory by luck, but at a misaligned address).

On some platforms, the hardware handles misaligned access, but still at somewhat of a cost compared to aligned access. For instance, two memory accesses may have to be made instead of one.

C compilers tend to enforce alignment when allocating struct members and variables even for target machines that don't enforce alignment. This is likely done for various reasons like performance, and compatibility.

Kaz
  • 55,781
  • 9
  • 100
  • 149
2

Different sizes due to padding and alignment.

Also, does this imply that we should always make sure to reorder struct members to minimize padding?

Probably not necessary, all needed is the packed attribute, and these would all be the same 16 bytes. Assumed gcc.

struct __attribute__((__packed__)) test2 {
    long y;
    short int b;
    int a;
    short int t;
};

Note, however, there is a tradeoff between size and speed:

When a structure is packed, these paddings are not inserted. The compiler has to generate more code (which runs slower) to extract the non-aligned data members, and also to write to them.

What is a "packed" structure in C?

artm
  • 17,291
  • 6
  • 38
  • 54
  • 2
    Except misaligned loads do bad things to the performance of the program, don't they? – Jerry Jeremiah Dec 03 '19 at 06:32
  • __attribute__((__packed__)) is really helpful! But does this now mean I should just throw this in every struct? – jimmyhuang0904 Dec 03 '19 at 06:33
  • @jimmyhuang0904 no. The compiler doesn't add padding just because, but to keep values aligned to their natural address boundaries. Depending from the platform, accessing a misaligned field may incur in a performance hit. If you want to minimize padding, just keep members sorted bigger to smaller. – Matteo Italia Dec 03 '19 at 06:37
  • 1
    @jimmyhuang0904 No! You should put the elements so that they align the best. Size order, bigger first. That attribute is only when you really need it and know why you need it. – Sami Kuhmonen Dec 03 '19 at 06:37
  • Thanks! I get the best code practices now. I just had to play the devils advocate questioning that to make sure I understand it completely. – jimmyhuang0904 Dec 03 '19 at 06:39
2

To align data properly:

  • long should start at an address that is a multiple of 8
  • int should start at an address that is a multiple of 4
  • short should start at an address that is a multiple of 2

Test1 aligns everything with no padding:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|           y           |     a     |  b  |  t  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Test2 needs to add padding after b so that a is on a 4 byte boundary. But the compiler wants the structure to be a multiple of 8 bytes so it can be tessellated (for example, in an array) so it adds padding at the end:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|           y           |  b  |  p  |     a     |  t  |        p        |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Test3 needs to add padding after a so that y is on a 8 byte boundary. But, again, the compiler wants the structure to be a multiple of 8 bytes so it can be tessellated (for example, in an array) so it adds padding at the end:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|     a     |     p     |           y           |  b  |  t  |     p     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

I think the part that is concerning you is the padding at the end of the structure. If you were going to make an array of one of these the compiler would have to put padding somewhere in each one so that the members would be aligned properly at each index but since the first data element has to have the same address as the structure, the compiler is adding the extra padding to the end. It probably doesn't need to do this if you will only ever have discrete variables of these types but its probably easier for the compiler to just do it unconditionally.

Jerry Jeremiah
  • 9,045
  • 2
  • 23
  • 32