C / DYNAMIC MEMORY
malloc, sizeof and the allocation idiom that scales
Allocate heap blocks by deriving the byte count from the destination pointer, so p = malloc(n * sizeof *p) stays correct when the type changes.
What you will learn
- Allocate with p = malloc(n * sizeof *p) so the size follows the declaration of p
- Rely on sizeof not evaluating its operand: sizeof *p is safe on a NULL pointer
- Test malloc's result against NULL before the first write, and print sizes with %zu
- Keep the element count in your own variable; sizeof p only sizes the pointer
Understanding malloc, sizeof and the allocation idiom that scales
malloc has exactly one input: a count of bytes. It does not know what you intend to store there, which is why its result type is void * — the address of a block of raw storage with no type attached to it. Since C gives you no way to say "room for four struct point objects", you convert objects to bytes yourself, and sizeof is the only portable way to do that conversion, because the byte count of a struct point is not a number you can hard-code across compilers and platforms.
sizeof is an operator, not a function, and apart from variable-length array types it is resolved entirely at compile time. Its operand is not evaluated, so sizeof *pts asks how large the type pts points to is, without ever reading through pts. That is why the expression is legal and harmless while pts is still uninitialized or NULL, which is precisely its state on the line where you call malloc. The result has type size_t, so print it with %zu rather than %d.
The reason to write n * sizeof *pts instead of n * sizeof(struct point) is maintenance, not keystrokes. The first form derives the size from the destination, so if the declaration later becomes struct point3d *pts, the request grows with it. The second form names the type a second time, and because malloc takes a plain number and returns void *, no compiler can notice when that second name goes stale: long *v = malloc(n * sizeof(int)); compiles without one diagnostic and hands you a block half the size you needed. For the same reason, leave malloc's result uncast — a cast is a third place to repeat the type.
<stdio.h>
<stdlib.h>
struct point { double x, y; };
int main(void)
{
size_t n = 4;
struct point *pts = malloc(n * sizeof *pts); /* bytes, not objects */
if (pts == NULL) {
fprintf(stderr, "out of memory\n");
return 1;
}
for (size_t i = 0; i < n; i++) {
pts[i].x = (double)i;
pts[i].y = (double)i * 2.0;
}
printf("sizeof *pts = %zu\n", sizeof *pts);
printf("request bytes = %zu\n", n * sizeof *pts);
printf("pts[3] = (%.1f, %.1f)\n", pts[3].x, pts[3].y);
/* sizeof does not evaluate its operand, so this never dereferences */
struct point *never = NULL;
printf("never == NULL: %d, sizeof *never = %zu\n",
never == NULL, sizeof *never);
free(pts);
return 0;
}
malloc counts bytes, not objects, so let the destination pointer supply the size: n * sizeof *p.
Worked examples
sizeof measures types, not allocations
Shows why an array can report its own size but a malloc'd pointer cannot.
<stdio.h>
<stdlib.h>
int main(void)
{
int arr[10];
int *heap = malloc(10 * sizeof *heap);
if (heap == NULL)
return 1;
printf("sizeof arr = %zu\n", sizeof arr);
printf("elements in arr = %zu\n", sizeof arr / sizeof arr[0]);
printf("sizeof heap = %zu\n", sizeof heap);
printf("sizeof *heap = %zu\n", sizeof *heap);
free(heap);
return 0;
}
Example explained
Line 1sizeof arr is 40 because arr has type int[10]; the length is part of the type, not stored in memory.
Line 2sizeof arr / sizeof arr[0] recovers the count 10 only for a real array, and silently breaks if arr becomes a pointer.
Line 3sizeof heap prints 8, the width of a pointer: an address to 40 bytes is indistinguishable from an address to 4.
Line 4sizeof *heap is 4, the one number malloc needs, which is why the idiom puts a dereference inside sizeof.
Byte buffers where sizeof is already 1
Demonstrates that for char the multiply by sizeof is a no-op, so the only arithmetic left is the +1 for the terminator.
<stdio.h>
<stdlib.h>
<string.h>
static char *copy_str(const char *src)
{
size_t len = strlen(src);
char *copy = malloc(len + 1); /* sizeof *copy is 1 */
if (copy == NULL)
return NULL;
memcpy(copy, src, len + 1);
return copy;
}
int main(void)
{
char *c = copy_str("malloc");
if (c == NULL)
return 1;
printf("copy = %s, bytes requested = %zu\n", c, strlen(c) + 1);
printf("sizeof *c = %zu\n", sizeof *c);
free(c);
return 0;
}
Example explained
Line 1strlen counts 6 characters and excludes the terminator, so the request is len + 1 = 7 bytes.
Line 2sizeof *c is 1 by definition of the standard, so len + 1 needs no multiply at all.
Line 3memcpy copies len + 1 bytes so the '\0' travels with the text; copying only len bytes leaves an unterminated string.
The idiom through a member expression
Applies sizeof *p to both a single object and a member pointer, with no type name written twice.
<stdio.h>
<stdlib.h>
struct vec {
size_t len;
int *data;
};
int main(void)
{
struct vec *v = malloc(sizeof *v); /* one object: no multiply */
if (v == NULL)
return 1;
v->len = 5;
v->data = malloc(v->len * sizeof *v->data);
if (v->data == NULL) {
free(v);
return 1;
}
for (size_t i = 0; i < v->len; i++)
v->data[i] = (int)(i * i);
printf("len=%zu data[4]=%d\n", v->len, v->data[4]);
printf("header bytes=%zu element bytes=%zu\n",
sizeof *v, v->len * sizeof *v->data);
free(v->data);
free(v);
return 0;
}
Example explained
Line 1malloc(sizeof *v) has no count factor because one object is being allocated; the implicit count is 1.
Line 2*v->data has type int, so sizeof *v->data is 4 and the idiom works unchanged through member access.
Line 3The header is 16 bytes while the elements need 20: sizeof *v measures the struct only, never the block its member points to.
Important notes
malloc leaves the bytes indeterminate; reading an element before writing it is undefined behaviour, not a read of zero.
malloc(0) may return NULL or a unique pointer that you may free but not dereference, so NULL from a zero-length request is not a failure signal — guard the call with count > 0.
Common mistakes
Writing malloc(n) for an int *p when n elements were meant: the block is a quarter of the needed size, and the loop that fills it writes past the end and corrupts the heap long before the program crashes.
Writing sizeof p instead of sizeof *p: every element is sized as a pointer (8 bytes on 64-bit), which over-allocates for int and under-allocates by half for a 16-byte struct, with no warning either way.
Using the block before checking for NULL: when the request fails, the first pts[0].x = ... dereferences a null pointer, and the crash points at the loop rather than at the allocation that failed.
Try it yourself
Change, predict, then run
Allocate room for 6 doubles with double *v = malloc(6 * sizeof *v);, fill it with 1, 2, 4, 8, 16, 32, and print each value with %.1f plus the requested byte count with %zu. Then change only the declaration to float *v and confirm the printed byte count halves with no other edit.
Open the C workspaceCheck your understanding
A program contains struct node *head = malloc(sizeof(struct link)); where struct link is smaller than struct node. Why does the compiler accept this without complaint?
- The compiler substitutes sizeof(struct node) for sizeof(struct link) because of the assignment target.
- It does not compile: assigning void * to struct node * requires an explicit cast in C.
- malloc receives only a byte count and returns void *, which converts to any object pointer type, so nothing connects the size to head's type.
- malloc inspects the type on the left of the assignment and quietly rounds an undersized request up.
Show answer
By the time malloc is called, sizeof(struct link) is just a size_t value; malloc has no way to learn what the caller plans to store, and its void * result converts implicitly to struct node * with no diagnostic, so the undersized block is accepted and overflows on first use. Option 1 tempts anyone coming from C++, where that conversion does need a cast, but in C the implicit conversion from void * is legal, which is exactly why deriving the size with sizeof *head is the safer habit.