memory – How do these two ways of enforcing alignment work together?

Computer memory does not care about types, but does care about word size. A word consists of multiple bytes. The CPU’s instruction set has instructions for reading or writing (or atomically modifying) a word of memory, but not all addresses can be used for such instructions: the pointer used for such instructions must be suitably aligned. Which alignments are allowed depends on the exact instruction set.

An address is n-byte aligned if the address is a multiple of n. Equivalently, the last log(n) bits in the address should be zero.

Assuming a word size of 8 and an alignment of n=8, here are some memory addresses of which some are aligned:

  0               1                 addr aligned

1         XXXXXXXX                  0x08 yes
2          XXXXXXXX                 0x09 no
3              XXXXXXXX             0x0D no
4                XXXXXXXX           0x0F no
5                 XXXXXXXX          0x10 yes
6                  XXXXXXXX         0x11 no

In pseudo-assembly notation, mov eax, (10H) would be an aligned access, but mov eax, (11H) would not be.

We want any accesses to variables to be aligned. For example, stacks are usually aligned to 16 bytes, and on Linux the malloc() function typically returns word-aligned pointers. But we want global variables to be aligned as well.

The layout of global variables is not decided at run time through assembly instructions, but is largely determined at compile time by creating a data segment in the executable. This data segment is then copied verbatim into memory.

For example, we might want to have two initialized variables: a zero-terminated string "Hello" and an 8-byte number with value 5. Even if we start at an aligned address, not all variables will end up being aligned:

  0               1                addr

          Hello0                   0x08
                50000000           0x0E !!!

So instead, the compiler or assembler must insert padding so that the variable is aligned (= can be accessed with an aligned memory access):

  0               1                addr

          Hello0                   0x08
                --                      unused bytes as padding
                  50000000         0x10

So in summary: the .align 8 directive tells the assembler to insert padding so that all variables/objects have aligned addresses. This allows them to be accessed via aligned memory addresses. The compile-time layout and the run-time accesses don’t have precedence over each other, but they work together.

Types are sometimes viewed as sets, where the members of the set are instances of the type. Thus, the term “every object within the type” talks about objects that have a type. However, variables within an object also need alignment. So the compiler will also insert padding as needed between members of a structure and at the end of the structure. The result is that this structure will likely have sizeof(struct T) == 24:

struct T {
  bool x;   /* 1 byte */
            /* 7 byte inserted padding for size_t */
  size_t y; /* 8 byte */
  bool z;   /* 1 byte */
            /* 7 byte inserted padding for struct T */