C / DYNAMIC MEMORY
Alignment, padding and why malloc returns aligned blocks
Predict a struct's size and member offsets from alignment rules, and know exactly what alignment guarantee malloc gives you and when you need aligned_alloc.
What you will learn
- Predict struct size and member offsets by hand, then confirm with sizeof and offsetof
- Reorder members strictest alignment first to cut padding out of a struct
- State what malloc guarantees: alignment for any type, alignof(max_align_t)
- Use aligned_alloc when you need 32- or 64-byte over-alignment
Understanding Alignment, padding and why malloc returns aligned blocks
Every complete object type in C carries an alignment requirement: a small power of two that any address holding such an object must be a multiple of. _Alignof(int) is 4 on x86-64 and _Alignof(double) is 8, so an int may live at 0x1004 but not at 0x1005. Hardware is the reason: load units fetch naturally aligned words, a misaligned int can straddle two cache lines and cost two accesses, and on strict targets such as some ARM and RISC-V configurations it traps. The standard does not merely call this slow: reading or writing an object through a pointer that is not correctly aligned is undefined behaviour, so an optimizer may emit an aligned vector instruction and let the program fault.
A struct must give every member an offset that satisfies that member's own alignment, and the compiler may not reorder members, so it inserts padding instead. The struct's alignment is the strictest alignment among its members, and its size is rounded up to a multiple of that alignment; the trailing padding exists because array elements are contiguous, so element i sits at i * sizeof(T) and would drift off a legal address if the size were not a multiple. That is why declaration order changes sizeof: a gap appears wherever a loosely aligned member is followed by a stricter one. offsetof and sizeof let you read the real layout rather than guess at it.
malloc receives a byte count and nothing else, no type and no alignment argument, so the only contract it can honour is to return an address suitable for any object with fundamental alignment, which the standard expresses as alignof(max_align_t): 16 on x86-64 glibc, 8 on typical 32-bit targets. That guarantee is precisely what makes assigning the returned void* to any object pointer type legal, and it is why even malloc(1) hands back a 16-byte-aligned slot. The guarantee stops at fundamental alignment: a 32-byte AVX vector or a 64-byte cache-line-aligned buffer is over-aligned and needs aligned_alloc, and any pointer you compute by adding an odd byte offset inside a malloc block loses the guarantee immediately.
<stdalign.h>
<stddef.h>
<stdint.h>
<stdio.h>
<stdlib.h>
struct Packet {
char flag;
int length;
char tag;
double stamp;
};
int main(void)
{
printf("alignof(char) = %zu\n", alignof(char));
printf("alignof(int) = %zu\n", alignof(int));
printf("alignof(double) = %zu\n", alignof(double));
printf("offsets: flag=%zu length=%zu tag=%zu stamp=%zu\n",
offsetof(struct Packet, flag),
offsetof(struct Packet, length),
offsetof(struct Packet, tag),
offsetof(struct Packet, stamp));
size_t used = sizeof(char) + sizeof(int) + sizeof(char) + sizeof(double);
printf("sizeof(struct Packet) = %zu, members use %zu, padding = %zu\n",
sizeof(struct Packet), used, sizeof(struct Packet) - used);
printf("alignof(struct Packet) = %zu\n", alignof(struct Packet));
void *p = malloc(1);
if (p == NULL)
return 1;
printf("malloc(1) address is a multiple of %zu: %s\n",
alignof(max_align_t),
(uintptr_t)p % alignof(max_align_t) == 0 ? "yes" : "no");
free(p);
return 0;
}
Alignment is a per-type constraint on addresses, and both struct padding and malloc's fundamental-alignment promise exist so that every object lands on an address its type permits.
Worked examples
Same members, two sizes
Shows that member declaration order decides how much padding a struct carries.
<stdalign.h>
<stddef.h>
<stdio.h>
struct Wide {
char a;
double b;
char c;
int d;
};
struct Tight {
double b;
int d;
char a;
char c;
};
int main(void)
{
printf("Wide: size=%zu align=%zu offsets a=%zu b=%zu c=%zu d=%zu\n",
sizeof(struct Wide), alignof(struct Wide),
offsetof(struct Wide, a), offsetof(struct Wide, b),
offsetof(struct Wide, c), offsetof(struct Wide, d));
printf("Tight: size=%zu align=%zu offsets b=%zu d=%zu a=%zu c=%zu\n",
sizeof(struct Tight), alignof(struct Tight),
offsetof(struct Tight, b), offsetof(struct Tight, d),
offsetof(struct Tight, a), offsetof(struct Tight, c));
printf("1000 objects: %zu vs %zu bytes\n",
1000 * sizeof(struct Wide), 1000 * sizeof(struct Tight));
return 0;
}
Example explained
Line 1b needs an offset that is a multiple of 8, so seven padding bytes sit between a at 0 and b at 8 in Wide.
Line 2d needs only a multiple of 4, so after c at 16 it lands at 20 and leaves offsets 17 to 19 unused.
Line 3In Tight the last used byte is 13, and bytes 14 and 15 are trailing padding that rounds sizeof up to a multiple of the struct's 8-byte alignment.
Line 4Nothing but the declaration order changed, and the layout rules turned that into 8 wasted bytes per object.
What malloc promises and what it does not
Compares malloc's fundamental alignment with an explicit 64-byte request, and shows that an interior byte pointer is not automatically aligned.
<stdalign.h>
<stddef.h>
<stdint.h>
<stdio.h>
<stdlib.h>
int main(void)
{
void *m = malloc(256);
void *a = aligned_alloc(64, 256);
if (m == NULL || a == NULL)
return 1;
printf("malloc block %% %zu == 0 : %s\n",
alignof(max_align_t),
(uintptr_t)m % alignof(max_align_t) == 0 ? "yes" : "no");
printf("aligned_alloc block %% 64 == 0 : %s\n",
(uintptr_t)a % 64 == 0 ? "yes" : "no");
char *bytes = m;
printf("bytes+0 ok for double? %s\n",
(uintptr_t)(bytes + 0) % alignof(double) == 0 ? "yes" : "no");
printf("bytes+1 ok for double? %s\n",
(uintptr_t)(bytes + 1) % alignof(double) == 0 ? "yes" : "no");
free(a);
free(m);
return 0;
}
Example explained
Line 1alignof(max_align_t) is 16 here, and every malloc result is a multiple of it because malloc has to serve the strictest fundamental type.
Line 2aligned_alloc(64, 256) asks for more than malloc promises; the size is 256 rather than 250 because the alignment must divide the size in C11 and C17.
Line 3bytes + 1 lies inside a perfectly aligned block yet is itself odd, so *(double *)(bytes + 1) is undefined behaviour even though x86 tolerates the load.
Line 4The aligned_alloc block is released with plain free; there is no separate aligned_free function.
Important notes
aligned_alloc requires an alignment the implementation supports, and in C11 and C17 the size must be an integral multiple of that alignment; C23 relaxed the size rule, so pass a multiple anyway if the code must build everywhere.
Packed layouts (#pragma pack, __attribute__((packed))) delete padding by producing members the hardware considers misaligned; they belong in wire and file format code, not in general size tuning.
Common mistakes
Sizing an allocation by adding member sizes, as in malloc(sizeof(char) + sizeof(int) + sizeof(double)), which reserves 13 bytes for a struct that really occupies 24 and corrupts the heap on the first write to the last member.
Comparing two structs with memcmp: assigning members never initialises padding bytes, so objects with identical field values can compare unequal, and byte-wise hashing of the struct gives unstable results.
Parsing a byte buffer with casts such as *(int *)(buf + 1): it appears to work on x86, faults on strict-alignment ARM, and is flagged by UBSan, whereas memcpy into a typed local is always correct.
Try it yourself
Change, predict, then run
Define struct Rec { char tag; double x; char kind; double y; }, print offsetof for each member together with sizeof, then reorder the members so sizeof falls from 32 to 24 and say which offsets the remaining padding occupies.
Open the C workspaceCheck your understanding
On x86-64 glibc, malloc(1) returns an address that is a multiple of 16 even though a single byte has an alignment requirement of 1. What best explains that?
- The C standard fixes heap granularity at 16 bytes on every implementation.
- 16 bytes is the smallest block the free lists can track, so the alignment is a side effect of block size.
- malloc cannot know which type you will store in the block, so it must satisfy the strictest fundamental alignment, alignof(max_align_t).
- The 16-byte boundary is what lets free() locate the bookkeeping header stored just before the returned pointer.
Show answer
malloc takes only a byte count, so the returned pointer has to be usable for any object with fundamental alignment; that is exactly alignof(max_align_t), and honouring it for one byte costs nothing. The first option is tempting because 16 really is the number you observe here, but the standard fixes no constant: alignof(max_align_t) is 8 on typical 32-bit targets, so code that hardcodes 16 is wrong on those platforms.