C Memory Alignment and Padding: Understand Struct Layout and Efficient Memory Use
Memory alignment and padding are important concepts when working with C structures, arrays, low-level programming, embedded systems, and performance-sensitive applications. Although the C language does not require a particular byte-level layout for every type, implementations commonly align objects according to their requirements and insert padding where necessary.
What Is Memory Alignment?
Memory alignment means placing an object at an address suitable for its type. For example, a platform may require or prefer an integer object to begin at an address divisible by 4, while a larger type may require stronger alignment.
The exact alignment requirements are implementation-dependent. You should therefore avoid assuming that a particular C type always has a specific alignment or size on every platform.
Why Does Alignment Matter?
Proper alignment can allow a processor to access data efficiently and, on some architectures, may be required for certain types. Incorrect assumptions about alignment can lead to inefficient code, implementation-specific behavior, or undefined behavior when objects are accessed through improperly aligned pointers.
What Is Padding?
Padding is unused space inserted by the compiler between or after structure members so that the members satisfy alignment requirements and the structure itself can be correctly aligned when stored in an array.
A Simple Structure Example
#include <stdio.h>
struct Example
{
char c;
int i;
};
int main(void)
{
printf("sizeof(struct Example) = %zu\n", sizeof(struct Example));
return 0;
}
On many common systems, char occupies one byte and int has stronger alignment requirements. The compiler may therefore insert padding between c and i.
Visualizing Structure Layout
A typical layout might look conceptually like this, although the exact result is implementation-dependent.
struct Example
{
char c; /* 1 byte */
/* padding */
/* padding */
/* padding */
int i; /* commonly 4 bytes */
};
Using offsetof
The offsetof macro from stddef.h can be used to determine the offset of a structure member from the beginning of the structure.
#include <stddef.h>
#include <stdio.h>
struct Example
{
char c;
int i;
double d;
};
int main(void)
{
printf("c: %zu\n", offsetof(struct Example, c));
printf("i: %zu\n", offsetof(struct Example, i));
printf("d: %zu\n", offsetof(struct Example, d));
printf("size: %zu\n", sizeof(struct Example));
return 0;
}
Member Order Affects Padding
The order of members can change the amount of padding required. Reordering members with larger alignment requirements together can sometimes reduce wasted space.
struct PoorLayout
{
char a;
double b;
char c;
int d;
};
struct BetterLayout
{
double b;
int d;
char a;
char c;
};
The second layout may require less padding on many implementations, but the exact sizes must be measured with sizeof rather than assumed.
Tail Padding
Padding can also appear at the end of a structure. This is especially important because structures placed in an array must ensure that every element begins at an address satisfying the structure's alignment requirements.
struct Item
{
int value;
char flag;
};
int main(void)
{
struct Item items[10];
/* sizeof(struct Item) determines the distance
between consecutive elements. */
return 0;
}
Why Structures Can Be Larger Than Their Members
The result of sizeof(struct Type) can be greater than the sum of sizeof each member because padding bytes may exist between members or at the end.
#include <stdio.h>
struct Data
{
char c;
int i;
};
int main(void)
{
size_t members = sizeof(char) + sizeof(int);
size_t structure = sizeof(struct Data);
printf("Member sizes: %zu\n", members);
printf("Structure size: %zu\n", structure);
return 0;
}
Alignment of a Type
C11 provides the _Alignof operator, which can be used to query the alignment requirement of a type.
#include <stdio.h>
int main(void)
{
printf("char alignment: %zu\n", _Alignof(char));
printf("int alignment: %zu\n", _Alignof(int));
printf("double alignment: %zu\n", _Alignof(double));
return 0;
}
Using alignof in Modern C
The alignof keyword from stdalign.h can provide a more readable spelling of the alignment query in C11 implementations that support it.
#include <stdio.h>
#include <stdalign.h>
int main(void)
{
printf("int alignment: %zu\n", alignof(int));
printf("double alignment: %zu\n", alignof(double));
return 0;
}
Structure Alignment
A structure's alignment requirement is generally sufficient to ensure that all of its members can be placed correctly. Implementations may use the strictest alignment requirement among its members as part of determining the structure's alignment.
Alignment and malloc
The memory returned by malloc is suitably aligned for any object type with a fundamental alignment requirement. This allows dynamically allocated storage to be used for ordinary C objects after appropriate conversion and initialization.
#include <stdio.h>
#include <stdlib.h>
struct Data
{
int value;
double measurement;
};
int main(void)
{
struct Data *data = malloc(sizeof *data);
if (data == NULL)
{
return 1;
}
data->value = 10;
data->measurement = 3.14;
printf("%d %.2f\n", data->value, data->measurement);
free(data);
return 0;
}
C11 _Alignas
C11 provides _Alignas for specifying an alignment requirement for an object or member where the language and implementation permit the requested alignment.
#include <stdio.h>
_Alignas(16) int alignedValue;
int main(void)
{
printf("Address: %p\n", (void *)&alignedValue);
return 0;
}
Checking Alignment at Runtime
A pointer can be converted to uintptr_t when uintptr_t is available, allowing a program to inspect an address numerically. This is useful for diagnostics and experiments.
#include <stdint.h>
#include <stdio.h>
int main(void)
{
int value;
uintptr_t address = (uintptr_t)&value;
printf("Address: %p\n", (void *)&value);
printf("Address value: %ju\n", (uintmax_t)address);
return 0;
}
Packed Structures
Some compilers provide non-standard attributes or pragmas for reducing or eliminating structure padding. These facilities can be useful for specific binary formats or hardware interfaces, but they can also produce inefficient or unaligned accesses.
/* Compiler-specific example; not portable ISO C */
struct __attribute__((packed)) PackedData
{
char c;
int i;
};
Packed structures should not be used merely to make every structure smaller. On some systems, accessing an unaligned member can be slower or problematic. Use compiler-specific packing only when the external data layout actually requires it.
Padding and Binary File Formats
A common mistake is assuming that a C structure can always be written directly to a file and later read as an identical structure. Padding, byte order, type sizes, and representation can make this non-portable.
struct Header
{
char type;
int length;
};
/* Writing the raw structure representation can include
implementation-defined padding bytes. */
For portable binary formats, serialize individual fields using a precisely defined representation instead of depending on the compiler's structure layout.
Padding and Network Protocols
Network protocols generally define exact byte layouts. C structures should therefore not automatically be treated as wire-format definitions. Explicit serialization and deserialization are usually safer and more portable.
Bit-Fields and Layout
Bit-fields can reduce storage for certain data representations, but their allocation order, alignment, and representation can be implementation-defined. They should not be relied upon for portable network or file formats without carefully controlling the implementation.
Alignment and Performance
Reducing unnecessary padding can improve memory usage and sometimes cache behavior, especially when large arrays of structures are involved. However, smaller is not automatically faster. A layout that provides naturally aligned members may be preferable to a tightly packed layout that causes inefficient accesses.
Array of Structures Example
#include <stdio.h>
struct Point
{
double x;
double y;
};
int main(void)
{
struct Point points[1000];
printf("One point: %zu bytes\n", sizeof(struct Point));
printf("All points: %zu bytes\n", sizeof(points));
return 0;
}
When structures are stored in large arrays, even a small amount of padding per object can add up significantly.
Array of Structures vs Structure of Arrays
For performance-sensitive programs, the data organization itself can matter. An array of structures stores all fields for each object together, while a structure of arrays stores each field in a separate array.
/* Array of structures */
struct Particle
{
float x;
float y;
float mass;
};
struct Particle particles[1000];
/* Structure of arrays */
struct Particles
{
float x[1000];
float y[1000];
float mass[1000];
};
Neither representation is universally better. The right choice depends on access patterns, cache behavior, vectorization opportunities, and the surrounding application.
Inspecting Structure Layout
A useful debugging technique is to print sizeof and offsetof values for important structures.
#include <stddef.h>
#include <stdio.h>
struct Record
{
char id;
int count;
double value;
};
int main(void)
{
printf("sizeof = %zu\n", sizeof(struct Record));
printf("id offset = %zu\n", offsetof(struct Record, id));
printf("count offset = %zu\n", offsetof(struct Record, count));
printf("value offset = %zu\n", offsetof(struct Record, value));
return 0;
}
Static Assertions for Layout Assumptions
If a program intentionally depends on a particular implementation's layout, _Static_assert can detect unexpected changes during compilation.
#include <stddef.h>
struct Header
{
unsigned int type;
unsigned int length;
};
_Static_assert(offsetof(struct Header, length) == sizeof(unsigned int),
"Unexpected Header layout");
Common Mistakes
- Assuming sizeof a structure equals the sum of its members
- Assuming all types have the same alignment on every platform
- Treating compiler-specific packed structures as portable C
- Writing raw structures directly to portable binary files
- Ignoring tail padding when calculating array sizes
- Assuming a smaller structure is always faster
- Using unaligned pointers or casts without understanding their requirements
Best Practices
- Use sizeof and offsetof instead of guessing structure layout
- Group members thoughtfully when memory usage matters
- Keep naturally aligned members where practical
- Use _Alignof or alignof to inspect alignment requirements
- Avoid compiler-specific packing unless an external layout requires it
- Serialize data explicitly for portable file and network formats
- Use static assertions when a specific implementation layout is a deliberate requirement
Quick Reference
| Feature | Purpose |
|---|---|
| sizeof | Reports the size of an object or type, including structure padding |
| offsetof | Reports the offset of a structure member |
| _Alignof | Queries the alignment requirement of a type in C11 |
| alignof | Convenient C11 alignment spelling through stdalign.h |
| _Alignas | Requests a specified alignment for an object or member |
| _Static_assert | Checks layout assumptions during compilation |
Practice Exercises
- Create several structures with different member orders and compare sizeof results
- Use offsetof to print the position of every member
- Find a structure where reordering members reduces its size on your platform
- Create an array of structures and calculate its total memory usage
- Experiment with _Alignof for common C types
- Use _Alignas to request stronger alignment
- Compare a normal structure with a compiler-specific packed structure
- Design a portable binary serialization format without relying on structure padding
Conclusion
Memory alignment and padding explain why C structures can occupy more memory than the sum of their members. By understanding alignment requirements, member offsets, tail padding, and structure layout, you can write more portable and memory-conscious C programs. When exact external layouts matter, measure the implementation and serialize data explicitly rather than relying on undocumented assumptions.