Why is the struct aligned to 4-bytes (32-bit) on 64-bit machine?

Viewed 625

I tried to understand some things about struct padding with this code:

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

struct azaza { // of course suboptimal arrangement of elements
     uint32_t addr1;
     uint32_t addr2;
     uint8_t tmp;
     uint32_t addr3;
     uint8_t flags;
};

int main(void) {
     printf("%d\n", sizeof(struct azaza));

     return 0;
}

The output is: 20,
but I expected 24, because my machine and my OS are 64-bit and I thought that the alignment should be on a 4-byte boundary. Why does the alignment of structure on x86-64 OS is on a 4-byte boundary?

3 Answers

The term “64-bit machine” is vague. Computers processors and systems have several features which may have sizes different from each other on the same machine, including:

  • The width of most processor registers.
  • The width of addresses.
  • The width of the data bus.
  • The width of the arithmetic logic units.

For the moment, let’s assume all of these are 64 bits. Even so, why would we require, say uint32_t to be aligned to 64 bits?

One reason to require alignment is to avoid splitting accesses across memory transfers. If the bus is 64 bits wide, the system is generally designed to access memory at multiples of eight bytes (64 bits). When the processor wants to read some memory, say from a 64-bit address, it sends only the first 61 bits to the memory device(s). (61 is a lot, but we have assumed everything is 64 bits in this machine.) The memory device(s) get all eight bytes matching those 61 bits—all eight combinations of the low three bits that we did not send. It gets eight bytes at a time because that is what fits on the bus, and we want to be efficient.

So, whenever the process reads from memory, it is always going to get eight bytes, and those bytes are going to be 64-bit aligned.

Now we can see that if a uint32_t started at some address, say xxx0101, where the x’s represent bits we do not care about, its four bytes would be at addresses xxx0101, xxx0110, xxx0111, and xxx1000. But that fourth byte is in a different group of eight. The first three are all in the same group, the one addressed by the initial bits xxx0. The last byte is in a new group, xxx1. To read this uint32_t, we have to do two reads from memory. That is inefficient.

However, if the uint32_t is at either address xxx0000 or xxx1000, its bytes are all within one group. They might be the first four or the last four bytes in that group, so we will need the processor to be able to select the first four or the last four from the eight it gets from memory, but it will take only one read from memory to get the bytes.

Therefore, four-byte alignment for uint32_t suffices to ensure it is well enough aligned that we only need one read to get it from memory.

There is very little reason to require eight-byte alignment. One reason could be that, if it were eight-byte aligned, we would not need the extra wires and switches in the processor to be able to select the first four or the last four bytes out of eight. We would only need to take the first four. But this slight advantage is hugely overwhelmed by the fact it would mean we can store only one uint32_t in every eight bytes. Half the memory would be wasted with padding. With four-byte alignment, we can read uint32_t objects pretty well, and we can read two at a time.

With uint8_t, eight-byte alignment would be even worse, we could have only one uint8_t in every eight bytes, wasting 87.5% of the memory.

For the most part, an object of length n bytes only needs to have an n-byte alignment in order to perform well with the hardware (supposing n is a power of two). That alignment will allow them to fit neatly into the bus and memory operations, whatever width they are.

Further, if the bus width is b and the object size is n, the alignment requirement may just be the lesser of b or n. Once an object is larger than the bus width, we are going to need multiple transfers to get it, and often nothing is gained by requiring more alignment than the bus width.

uint32_t is 4 byte , 2 * uint32_t = 8 byte , uint8_t is 1 byte but because the biggest variable size is 4 byte ,so the compiler pade the uint8_t to be in 4 byte,now we have 12 byte + uint32_t + uint8_t we get 20 byte . suppose that we have

struct azaza {
 uint32_t addr1;
 uint8_t tmp;
 uint8_t tmp1;
 uint8_t tmp2;
 uint32_t addr3;
 uint8_t flags;
};

the size became 4 + 3 byte in chunk of 4 byte + 4 +1 byte in chunk of 4 = 4 +4 +4 +4 = 16

struct azaza { 
 uint32_t tmp;
 uint8_t tmp1;
 uint8_t tmp2;
 uint8_t tmp3;
 uint64_t tmp4;
 uint8_t tmp5;
};

the biggest element is 8 byte tmptmptmptmptmp1tmp2tmp3-|tmp4tmp4tmp4tmp4tmp4tmp4tmp4tmp4|tmp5------- =24 byte

another example

  struct azaza {

 uint8_t t1;
 uint16_t t2;
 uint32_t t3;
 
};

the biggest element is 4 byte . consider - as empty block . t1-t2t2|t3t3t3t3 = 8 byte

Related