Question

When a struct member is a struct, its alignment and size are different from native types?

I found an interesting phenomenon when programming in C: when a member of a structure is a structure, the alignment requirement of this structure member is the size of its largest member. I didn't find relevant content in the C standard (if I am blind, please remind me), but mainstream compilers all implement it this way.

Let's look at the following code:

#include <stdio.h>
#include <stdint.h>

struct W {
    uint8_t hi;
    uint8_t lo;
};

struct S1 {
    uint8_t b1;
    uint16_t w1;
};

struct S2 {
    uint8_t b1;
    struct W w1;
};

int main(int argc, const char* argv[])
{
    printf("sizeof(uint16_t) : %zu\n", sizeof(uint16_t));
    printf("sizeof(struct W) : %zu\n", sizeof(struct W));
    printf("sizeof(struct S1): %zu\n", sizeof(struct S1));
    printf("sizeof(struct S2): %zu\n", sizeof(struct S2));

    printf("================================================\n");

    struct S1 s1 = { 0 };
    printf("&s1      : %p\n", &s1);
    printf("&s1.b1   : %p\n", &s1.b1);
    printf("&s1.w1   : %p\n", &s1.w1);

    struct S2 s2 = { 0 };
    printf("&s2      : %p\n", &s2);
    printf("&s2.b1   : %p\n", &s2.b1);
    printf("&s2.w1   : %p\n", &s2.w1);
    printf("&s2.w1.hi: %p\n", &s2.w1.hi);
    printf("&s2.w1.lo: %p\n", &s2.w1.lo);
}

The output is as follows:

sizeof(uint16_t) : 2
sizeof(struct W) : 2
sizeof(struct S1): 4
sizeof(struct S2): 3
================================================
&s1      : 0000003D4C97FCB4
&s1.b1   : 0000003D4C97FCB4
&s1.w1   : 0000003D4C97FCB6
&s2      : 0000003D4C97FCD4
&s2.b1   : 0000003D4C97FCD4
&s2.w1   : 0000003D4C97FCD5
&s2.w1.hi: 0000003D4C97FCD5
&s2.w1.lo: 0000003D4C97FCD6

When the size of member w1 is 2 bytes, why is sizeof(struct S2) smaller than sizeof(struct S1)? And struct S1 is 2-byte aligned but struct S2 is 1-byte aligned.

This code has been run on both MSVC and GCC with consistent results.

 3  115  3
1 Jan 1970

Solution

 2

The C requirement is just this (C17 6.7.2.1):

Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.

This means that each member is aligned based on its own type, but the compiler is free to do so as it pleases. And the compiler may add any number of padding bytes anywhere in the struct except at the very beginning.

The compiler is however required to allocate the members in a strict order where the first one is always on the lowest address (also from 6.7.2.1):

a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence

This requirement means that the compiler is unable to re-order the struct members to suit alignment better. The programmer is in charge of it.

In case of S1 the member b1 does not have an alignment requirement but w1 does. Therefore a padding byte has to be appended after b1 simply because b1 has size 1 which would force the next member in the struct to an odd address.

However, swapping the order of the members in this case wouldn't improve the situation, since the beginning of each struct will have to start at an alignment appropriate for the largest alignement requirement of a member of the struct. Since the compiler must make it possible for an array of structs to be allocated etc.

So if we swapped the order of b1 and w1 we'd merely move the padding bytes that was placed between the members to the end instead.

2024-07-03
Lundin

Solution

 1

None of this is guaranteed by the C standard, but in this particular case, the alignment requirements of the struct appears to be the alignment of the member with the largest alignment. A uint16_t has 2-byte alignment on your system, so when you place one after a uint8_t, the compiler adds a 1-byte padding to make sure the uint16_t is properly aligned, because some systems do not support dereferencing addresses to types that are not a multiple of the alignment requirement of the type.

When you only have a struct with more uint8_t, those members can be packed immediately after the uint8_t member because there are no members whose alignment requirements are violated.

Size is not alignment. Size is how much memory the type takes up, and alignment is multiple by which an address must be. If you have a pointer 4 byte integer and the pointer is not divisible by 4, that would be bad news on some systems.

Structures are collections of other types, so in theory, only the alignment requirements of their individual members need to apply, thus having an alignment smaller than their size.

2024-07-03
CPlus

Solution

 1

I found an interesting phenomenon when programming in C: when a member of a structure is a structure, the alignment requirement of this structure member is the size of its largest member.

This is not generally true. It is generally the member with the strictest alignment requirement, not the largest size, that governs the structure’s alignment requirement. Two simple examples are:

  • struct { char x[11]; }. Here, the largest member is 11 bytes, but the alignment requirement of this structure will normally be one byte, as the alignment requirement of the member is just one byte. In a structure that contains another structure, the contained structure could be quite large, but only its alignment requirement, not its size, will be used to affect the alignment requirement of the containing structure.
  • struct { uint64_t x; }. Consider a C implementation in which the hardware provides only 32-bit arithmetic instructions and the compiler supports 64-bit integer types by using multiple instructions. The compiler will store a uint64_t by storing two 32-bit integers. The hardware has only a four-byte alignment requirement, so the compiler has no need to align x to a multiple of eight bytes. Since x is effectively two four-byte items, it does not matter whether it is stored on an eight-byte boundary or not. The size of x will be eight bytes, but its alignment requirement will be four bytes, and so will that have the structure.

The C standard’s specifications require that the alignment requirement of a structure be at least as strict as the strictest alignment of its members. This is a logical necessity since alignment each member requires aligning the structure. Most C implementations will not use any stricter alignment requirement than that since there is normally no benefit in it.

This answer has the algorithm that is commonly used to lay out structures.

When the size of member w1 is 2 bytes, why is sizeof(struct S2) smaller than sizeof(struct S1)? And struct S1 is 2-byte aligned but struct S2 is 1-byte aligned.

The members of struct S2 have only one-byte alignment requirements: b1 is a uint8_t with a one-byte alignment requirement, and w1 is a struct W, which consists of two one-byte members each with a one-byte alignment requirement. So the alignment requirement of struct S2 is one byte, and its three bytes do not require any padding.

In struct S1, its w1 member has a two-byte alignment requirement. Because w1 is preceded by a single one-byte member, a byte of padding must be inserted to give w1 a two-byte alignment. This makes the total structure size four bytes, and the structure has a two-byte alignment requirement because that is the strictest alignment requirement of its members.

You can see the alignment requirement of a type by printing _Alignof (type) with %zu instead of printing the address of an object of that type. For example, printf("%zu\n", _Alignof (struct S1)); will print “2” even if some object of that type might happen to receive an address that is a multiple of eight bytes.

2024-07-03
Eric Postpischil