https://piazza.com/redirect/s3?bucket=uploads&prefix=attach%2Fk4emrbznyj46jr%2Fje60dvfpdsc1vj%2Fk729xd7wygus%2Falignment.PNG
For primitive, simple types like char, short, int, long, and pointers, the alignment is equal to the size. The alignment is the same as the size because all the bytes in the value are accessed at the same time whenever a load is performed.
For arrays, the alignment is equal to the element's alignment and the size is the element size * number of elements. So, an array of short has alignment 2, no matter how many elements it has. Note that we cannot access all bytes of an array at the same time; instead we have to access each element individually, and therefore the array only has to be aligned for each element.
For structs, the alignment is the largest alignment among all members, and the size is the total size after padding. These are both computed recursively.
The only possible alignments for anything (values, arrays, structs) are 1, 2, 4, or 8, since only long or void * (8-byte pointers) require 8-byte alignment.
A struct's alignment and size depend only on its own members. It won't change if it's embedded in a larger structure.
Let's work out your example bit by bit:
struct Y {
int d; // align = 4, size = 4
/* 4 bytes padding inserted to align next element */
struct X* e; // align = 8, size = 8
};
// => struct Y has align 8 (max of 4 and 8) and size 16
struct X {
int *a; // align = 8, size = 8
int b[4]; // align = 4, size = 16
struct Y c; // align = 8, size = 16
};
// => struct X has align 8 (max of 4 and 8) and size 40