—
Introduction – Why `sizeof` Is the Unsung Hero of C Programming
If you’ve ever been baffled by why a seemingly simple program crashes, why a buffer overrun sneaks past your tests, or why a piece of code behaves differently on a 32‑bit machine versus a 64‑bit one, chances are the culprit is a misunderstanding of memory size. In the C language, the `sizeof` operator is the tool that tells you exactly how many bytes an object or type occupies.
Think of `sizeof` as the “yardstick” every C programmer needs in their toolbox. It works at compile time, eliminates magic numbers, and makes your code portable across architectures. Yet, despite its power, many developers treat it as a one‑liner (`sizeof(int)`) and never explore its deeper capabilities.
In this 2,000‑word deep dive we’ll unpack everything you need to know about `sizeof`—from the basics to advanced tricks—so you can write safer, more efficient, and truly portable C code.
—
1. The Fundamentals: What `sizeof` Really Is
1.1. Compile‑time vs. Run‑time Evaluation
| Aspect | Details |
|——–|———|
| When does it evaluate? | Compile‑time for most expressions. The compiler knows the size of built‑in types, structs, unions, and arrays, so it replaces `sizeof` with a constant. |
| Exceptions | When applied to a variable‑length array (VLA) introduced in C99, `sizeof` is evaluated at run‑time because the length isn’t known until execution. |
| Why it matters | Compile‑time evaluation means no performance penalty. Run‑time evaluation only occurs for VLAs, which are relatively rare in modern code. |
1.2. Syntax and Basic Usage
“`c
size_t n = sizeof(int); // size of the type int
size_t m = sizeof variable; // size of the variable (no parentheses needed)
size_t k = sizeof (int[10]); // size of an array type (10 * sizeof(int))
“`
Key points
- Result type: `sizeof` yields a value of type `size_t`, an unsigned integer defined in “.
- Parentheses: Optional when you pass a variable (e.g., `sizeof x`). Required for a type name (e.g., `sizeof(int)`).
- No side effects: The operand is never evaluated. `sizeof(++i)` does not increment `i`.
- Maintainability: If the type of `arr` changes (e.g., from `int` to `long`), the allocation automatically adapts.
- Portability: On a platform where `int` is 2 bytes, the code still works without modification.
- `sizeof scores` → total bytes occupied by the whole array (`5 * sizeof(int)`).
- `sizeof scores[0]` → size of a single element (`sizeof(int)`).
- Division yields the element count, regardless of the element type.
- Padding: Compilers may insert padding bytes to align members. `sizeof` includes this padding.
- `#pragma pack` or compiler‑specific attributes (`attribute((packed))`) can control padding, but use them judiciously—misaligned accesses may degrade performance or cause faults on some CPUs.
- VLAs are optional in C11 and later; some compilers disable them by default.
- They allocate on the stack, so large VLAs can cause stack overflow.
- Use `sizeof` on a VLA only when you truly need its run‑time size; otherwise, prefer dynamic allocation with `malloc`.
- Interfacing with hardware registers where exact byte offsets matter.
- Serializing structs to binary files or network packets.
- The macro’s intent is explicit, and the compiler will catch mismatched types if you misuse it.
- It centralizes allocation logic, making future changes (e.g., adding zero‑initialization) trivial.
1.3. Why `size_t` Matters
`sizet` is guaranteed to be large enough to represent the size of any object that can be allocated. Using `int` or `long` instead can cause overflow on 64‑bit platforms. Always store the result of `sizeof` in a `sizet` variable.
“`c
size_t bytes = sizeof(struct MyStruct);
“`
—
2. Practical Applications – Using `sizeof` to Write Safer Code
2.1. Dynamic Memory Allocation Made Easy
When you allocate memory with `malloc`, `calloc`, or `realloc`, you must specify the number of bytes required. `sizeof` eliminates guesswork:
“`c
int arr = malloc(10 sizeof *arr); // allocate space for 10 ints
double matrix = calloc(rows, sizeof matrix); // rows of doubles, zero‑initialized
“`
Why this pattern is superior:
2.2. Determining the Length of an Array
A classic mistake is using a hard‑coded constant for array length:
“`c
int scores[5];
for (int i = 0; i < 5; ++i) { / … / }
“`
If you later change the array size, you must remember to update the loop condition—easy to forget. `sizeof` solves this:
“`c
int scores[5];
size_t len = sizeof scores / sizeof scores[0]; // 5
for (size_t i = 0; i < len; ++i) {
/ … /
}
“`
Explanation:
2.3. Struct and Union Size Checks
When working with binary file formats, network protocols, or hardware registers, you often need to know the exact layout size:
“`c
#pragma pack(push, 1) // Disable padding for the example
struct Header {
uint16_t magic;
uint32_t length;
uint8_t version;
};
#pragma pack(pop)
printf(“Header size: %zu bytesn”, sizeof(struct Header));
“`
Important nuance:
2.4. Avoiding Buffer Overflows
When copying memory, `memcpy` and `strncpy` require the exact number of bytes to move. `sizeof` ensures you never copy past the destination buffer:
“`c
char src[20] = “Hello, world!”;
char dst[20];
memcpy(dst, src, sizeof src); // copies exactly 20 bytes (including the terminating ”)
“`
If `dst` were smaller, you’d first compute the minimum of the two sizes:
“`c
sizet copylen = sizeof src < sizeof dst ? sizeof src : sizeof dst;
memcpy(dst, src, copy_len);
“`
2.5. Variable Length Arrays (VLAs) – When `sizeof` Becomes Dynamic
C99 introduced VLAs, where the length is determined at run‑time:
“`c
void process(int n) {
int vla[n]; // VLA, length determined by n
size_t bytes = sizeof vla; // evaluated at run‑time
printf(“VLA occupies %zu bytesn”, bytes);
}
“`
Caution:
—
3. Deep Dive – How `sizeof` Interacts with Types
3.1. Primitive Types and Platform Differences
| Type | Typical size on 32‑bit | Typical size on 64‑bit |
|——|————————|————————|
| `char` | 1 byte | 1 byte |
| `short` | 2 bytes | 2 bytes |
| `int` | 4 bytes | 4 bytes |
| `long` | 4 bytes | 8 bytes |
| `long long` | 8 bytes | 8 bytes |
| `float` | 4 bytes | 4 bytes |
| `double` | 8 bytes | 8 bytes |
| `pointer` (`void*`) | 4 bytes | 8 bytes |
Why it matters: Hard‑coding `4` for a pointer size works on 32‑bit systems but fails on 64‑bit. Use `sizeof(void*)` instead.
3.2. Pointers vs. Objects – The Classic Pitfall
A frequent source of bugs is confusing the size of a pointer with the size of the data it points to:
“`c
int p = malloc(10 sizeof *p); // correct
int p = malloc(10 sizeof p); // WRONG – allocates 10 sizeof(int)
“`
`sizeof p` yields the size of the pointer (4 or 8 bytes), not the size of the pointed‑to type. The idiomatic pattern `sizeof *p` avoids this mistake and stays correct even if the type of `p` changes.
3.3. Arrays Decay and `sizeof`
When an array name appears in most expressions, it decays to a pointer to its first element. However, `sizeof` is one of the few operators that prevents decay:
“`c
int arr[10];
printf(“%zun”, sizeof arr); // prints 40 (10 * sizeof(int) on a 32‑bit machine)
printf(“%zun”, sizeof *arr); // prints 4 (size of a single int)
“`
If you pass `arr` to a function, it decays to `int*`, and inside the function `sizeof` will give you the size of a pointer, not the original array.
“`c
void foo(int a[]) {
printf(“%zun”, sizeof a); // prints size of int* (4 or 8)
}
“`
Solution: Pass the array size as an additional argument, or use a macro that captures both:
“`c
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
void foo(int a[], size_t len) {
// use len safely
}
“`
3.4. Struct Padding and Alignment
Compilers align members to improve access speed. Consider:
“`c
struct Example {
char c; // 1 byte
int i; // 4 bytes, but placed at offset 4 due to alignment
short s; // 2 bytes, placed at offset 8
};
“`
`sizeof(struct Example)` will likely be 12 bytes (1 + 3 padding + 4 + 2 + 1 padding).
How to inspect layout:
“`c
#include
#include
struct Example e;
printf(“Offset of c: %zun”, offsetof(struct Example, c));
printf(“Offset of i: %zun”, offsetof(struct Example, i));
printf(“Offset of s: %zun”, offsetof(struct Example, s));
printf(“Total size: %zun”, sizeof e);
“`
When to care:
Controlling padding:
“`c
struct attribute((packed)) Packed {
char c;
int i;
short s;
};
“`
Remember: packed structs may cause unaligned memory accesses, which on some CPUs (e.g., ARM Cortex‑M) trigger a fault or severe performance loss.
3.5. Unions – Size Equals Largest Member
A union occupies enough space to hold its largest member:
“`c
union Data {
int i;
double d;
char c[16];
};
printf(“Union size: %zun”, sizeof union Data); // likely 16 (size of c[])
“`
Because all members share the same memory region, `sizeof` tells you the maximum storage requirement for any variant.
3.6. Function Pointers and `sizeof`
You can also apply `sizeof` to function pointers, though you rarely need to:
“`c
void (*func_ptr)(int) = NULL;
printf(“Function pointer size: %zun”, sizeof func_ptr); // same as any other pointer
“`
The size is independent of the function signature; it’s just the size of a pointer on the target architecture.
—
4. Advanced Techniques – Leveraging `sizeof` for Generic, Maintainable Code
4.1. Type‑Safe Macros with `sizeof`
Macros that allocate memory can be made type‑agnostic and safe:
“`c
#define MALLOC_ARRAY(cnt, type) ((type )malloc((cnt) sizeof(type)))
#define NEW(type) ((type *)malloc(sizeof(type)))
int *numbers = MALLOC_ARRAY(100, int); // allocates 100 ints
struct Node *node = NEW(struct Node); // allocates one Node
“`
Why this beats `malloc(cnt sizeof ptr)`:
4.2. Compile‑time Assertions Using `sizeof`
C11 introduced `Staticassert`. Before that, developers used tricks based on `sizeof` to generate compile‑time errors:
“`c
#define COMPILETIMEASSERT(expr, msg)
typedef char staticassertion##msg[(expr) ? 1 : -1]
COMPILETIMEASSERT(sizeof(long) == 8, longmustbe8bytes);
“`
If the condition is false, the array size becomes negative, causing a compilation failure. This technique is handy for validating struct layout assumptions.