Home > Software engineering >  Nested struct memory layout in c
Nested struct memory layout in c

Time:04-05

I have a stuct a of size 16 bytes and another struct b which contains a. Why is struct b of size 40 bytes? Where is the additional padding exactly?

typedef struct {
  
} a;

typedef struct {
  
  a x;
  
} b;

CodePudding user response:

The struct a has 4-byte alignment since that's the largest alignment of any of its members (i.e. float).

From there, the fields of b are laid out with the following offsets:

  • w: 0
  • padding: 1 - 3
  • x: 4 - 19
  • padding: 20 - 23
  • y: 24 - 31
  • z: 32 - 33
  • padding: 34 - 39

The member with the largest alignment is y which has 8-byte alignment. This results in 4 bytes of padding between x and y, as well as 6 bytes of padding at the end.

The padding can be minimized by moving z to between w and x. Then you would have:

  • w: 0
  • padding: 1
  • z: 2 - 4
  • x: 4 - 19
  • padding: 20 - 23
  • y: 24 - 31

For a total size of 32.

Alignment is of course entirely up to the implementation, but this is what you'll most likely see. The Lost Art of C Structure Packing goes into this in much more detail.

CodePudding user response:

The algorithm compilers typically use to lay out structures is described in this answer.

For your structure a and characteristics typical in current C implementations (described below):

  • The char w has an alignment requirement of 1 byte, so it needs no padding and is placed at offset 1. It occupies 1 byte.
  • The float x has an alignment requirement of 4 bytes, so 3 bytes are needed to bring the current offset from 1 byte to 4 bytes. Then it occupies 4 bytes, giving us 8 total so far.
  • The short int y has an alignment requirement of 2 bytes, so it needs no padding from the current offset of 8 bytes. It occupies 2 bytes, bringing the total to 10.
  • The float z has an alignment requirement of 4 bytes, so it needs 2 bytes to bring the offset from 10 bytes to 12 bytes. It occupies 4 bytes, bringing the total to 16.
  • The alignment requirement of the structure is 4 bytes (the strictest alignment requirement of its members), and the current total is 16 bytes, which is already a multiple of 4, so no padding at the end is needed.

Thus the size of a is 16 bytes.

For your structure b:

  • The char w has an alignment requirement of 1 byte, so it needs no padding and is placed at offset 1. It occupies 1 byte.
  • The a x has an alignment requirement of 4 bytes, so 3 bytes of padding are needed to bring the offset from 1 byte to 4 bytes. It occupies 16 bytes, bringing the total to 20.
  • The double y has an alignment requirement of 8 bytes, so 4 bytes of padding are needed to bring the offset from 20 bytes to 24. It occupies 8 bytes, bringing the total to 32.
  • The short int z has an alignment requirement of 2 bytes, so no padding is needed from the current offset of 32 bytes. It occupies 2 bytes, bringing the total to 34.
  • The alignment requirement of the structure is 8 bytes (the strictest alignment requirement of its members), so 6 bytes of padding is needed to bring the total size from 34 bytes to 40.

Thus the size of b is 40 bytes.

The characteristics used are:

  • char has size 1 byte and alignment requirement 1 byte. This is a fixed property of the C standard.
  • short int has size 2 bytes and alignment requirement 2 bytes.
  • float has size 4 bytes and alignment requirement 4 bytes.
  • double has size 8 bytes and alignment requirement 8 bytes. (Having an alignment requirement of 4 bytes instead of 8 would not be very weird; hardware might load and store doubles in 32-bit chunks and not care whether either of them were 8-byte aligned.)
  • The C implementation does not use more padding or stricter alignment than required by the sizes and alignment requirements of structure members. (This is not mandated by the C standard, but there is generally no reason to do otherwise.)

CodePudding user response:

You seem to understand why the size of a is 16 bytes, and I'll also assume that you see why a has an alignment requirement of 4 bytes (see the note about arrays in the discussion below on the alignment of b). From that, we can see that, in b, we will have 3 bytes of padding between w and x.

Now, on your platform (as on many/most), a double has a size and alignment requirement of 8 bytes – so, after the 20 bytes up to the end of x (1 for w, 3 for padding and 16 for x), we need to add 4 bytes padding between x and y, to align that double. Thus, we have now added 12 bytes to our total, giving a running size (up to y) of 32 bytes.

The z member takes another 2 bytes (running size of 34) but, as the alignment requirement of the overall b structure must be 8 bytes (so that, in an array of such, every element's y will remain properly aligned), we need a further 6 bytes to reach a size (40) that is a multiple of 8.

Here is a breakdown of the memory layout:

typedef struct {
    char w;     //  1 byte:  total =  1
         // 3 bytes padding: total =  4
    float x;    //  4 bytes: total =  8
    short int y;//  2 bytes: total = 10
         // 2 bytes padding: total = 12
    float z;    //  4 bytes: TOTAL = 16
} a;

typedef struct {
    char w;     //  1 byte:  total =  1
         // 3 bytes padding: total =  4
    a x;        // 16 bytes: total = 20
         // 4 bytes padding: total = 24
    double y;   //  8 bytes: total = 32
    short int z;//  2 bytes: total = 34
         // 6 bytes padding: TOTAL = 40
} b;
  • Related