This is best explained by example.
typedef struct s_ {
int a, b;
} s;
int add(s* l, s* r) { return l->a + l->b + r->a + r->b; }
void init(s* v) {
v->a = 1;
v->b = 2;
}
int array_like() {
// allocate enough space for s[2]
char *p = malloc(2 * sizeof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + sizeof(s));
init(p1);
init(p2);
return add(p1, p2);
}
int array_skip() {
// allocate enough space for s[3] and init only [0] and [2]
char *p = malloc(3 * sizeof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + 2 * sizeof(s));
init(p1);
init(p2);
return add(p1, p2);
}
int half_gap() {
// allocate enough space for 2.5 s objects and lay them
// out like [s1][gap of alignof(s) bytes][s2]
char *p = malloc(2 * sizeof(s) + _Alignof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + sizeof(s) + _Alignof(s));
init(p1);
init(p2);
return add(p1, p2);
}
For concreteness, consider a typical platform where sizeof(s) == 8 and alignof(s) == 4 – although the question should apply equally to platforms with different values.
The last three functions, array_like, array_skip and half_gap all perform a similar function: they create two objects of type s (a struct containing two ints) inside storage allocated by malloc. All three place the first s object at the start of the storage. They differ only in where they place the second object:
array_likeplaces it directly after the first object, i.e., at offset 8, so for a pointers* pto the start of the region the objects would be atp[0]andp[1].array_skipplaces itsizeof(s)bytes after the end of the first object, i.e., at offset 16, so for a pointers* pto the start of the region the objects would be atp[0]andp[2].half_gapplaces it_Alignof(s)bytes after the end of the first object, or 4 bytes after the first object, i.e., 12 bytes from the start of the storage. Note that this object is still correctly aligned on a boundary of 4 bytes, but it is not offset an integral number ofsizeof(s)bytes away from the first. You can't express the location of the second object with array-like notation as in the first two cases.
After that, each function writes to both members of both objects, and then reads from them.
Which of these functions are legal C11, and are all legal functions guaranteed to return the expected value of 6?