Every array you have written so far had its size baked in at compile time: int scores[10]; reserves ten slots whether you need three or three hundred. That works until the size is only known while the program runs - how many lines a file has, how many records the user enters. Dynamic memory solves that: you ask for the bytes you need, when you need them, and hand them back when you are done.
The tools are in <stdlib.h>:
#include <stdlib.h>
Why the Heap Exists
C gives your program two main regions of memory. Local variables live in automatic storage (the stack): the compiler decides their size, and they vanish the moment their function returns. Dynamic allocations live in the heap: you decide the size at runtime, and the block stays alive until you explicitly free it.
That second property is the real reason to use malloc. A function can allocate a block, return the pointer, and the memory is still valid in the caller - something a local array can never do. The two regions are compared in detail in stack vs heap.
malloc: Asking for Bytes
malloc takes a byte count and returns a void * pointing at that many bytes of uninitialized memory, or NULL if the request fails.
Four things in that short program are the whole discipline:
- The size expression is
n * sizeof *scores. Read it as "n of whateverscorespoints at". - The result is checked against
NULLbefore any use. - The block is used exactly like an array -
scores[i]works because indexing is pointer arithmetic. - It is freed once, and the pointer is then set to
NULL.
Note there is no cast on the return value. In C, void * converts to any object pointer automatically, and writing (int *)malloc(...) adds noise while hiding a missing <stdlib.h> include. (C++ requires the cast; C does not.)
The sizeof Idiom
Why sizeof *scores rather than sizeof(int)? Because it cannot go stale.
int *a = malloc(n * sizeof *a); /* n ints, whatever int is here */
long *b = malloc(n * sizeof *b); /* same line shape, right size */
/* the fragile form */
long *c = malloc(n * sizeof(int)); /* compiles, allocates too little */
The last line is a real bug that no compiler warns about: c is a long *, the allocation is sized for int, and every write past the first half runs off the end. With sizeof *c the size follows the declaration automatically.
sizeof *p does not dereference p - sizeof is evaluated at compile time from the type alone, so it is safe even when p is uninitialized or NULL.
Uninitialized Memory
malloc does not clear what it gives you. The bytes hold whatever was there before.
When you want the block zeroed for you, calloc does it in one step - see calloc and realloc.
free: Giving It Back
free(p) returns the block to the allocator. Three rules:
- Pass the exact pointer
mallocreturned, not one that has been advanced.free(p + 1)is undefined behavior. - Free each block exactly once. Twice is a double free, which corrupts the allocator's own bookkeeping.
- After freeing, the pointer is stale. Using it is a use-after-free.
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* use-after-free - undefined behavior */
free(p); /* double free - undefined behavior */
Neither line is required to crash immediately, which is what makes them dangerous: the program may run for minutes and then fail somewhere unrelated. The cheap defense is to blank the pointer:
free(p);
p = NULL;
free(NULL) is explicitly defined to do nothing, so a second free(p) after that is harmless, and *p becomes an immediate crash at the real fault site instead of quiet corruption.
A Dynamic Array, End to End
Here is the shape of a real allocation: read a count at runtime, size the block from it, use it, free it.
The cast to size_t on the count matters once sizes get large: n * sizeof *values with a plain int n can overflow before the multiplication ever reaches malloc, producing a block far smaller than intended. Multiplying in size_t avoids that.
Allocating Inside a Function
The heap's lifetime rule is what makes this legal - and what makes returning a local array illegal:
len + 1 leaves room for the null terminator - the same sizing rule as everywhere in strings. And notice the comment above the function: when a function returns allocated memory, who frees it is part of its contract. Writing that down is not paperwork; it is the only thing preventing a leak.
Structs on the Heap
The same idiom works for any type:
Note the second failure branch frees p before returning. Once a function holds more than one allocation, every error path has to release the ones already taken - the commonest source of the leaks described in memory leaks.
Checklist
- Always check
mallocforNULLbefore the first use. - Size with
n * sizeof *p, multiplying insize_t. - No cast on the return value in C.
mallocmemory is uninitialized; write before you read.- Free exactly once, with the original pointer, then set it to
NULL. - Every error path after an allocation must free what it already holds.
Frequently Asked Questions
What does malloc do in C?
malloc(n) asks for n bytes of memory from the heap and returns a pointer to the start of that block, or NULL if the request cannot be satisfied. The block lives until you pass that same pointer to free - unlike a local variable, it is not released when the function returns.
How do you use malloc and free in C?
Allocate with int *p = malloc(n * sizeof *p);, check if (p == NULL) before touching it, use it like an array, then free(p); exactly once when you are finished. Setting p = NULL afterwards turns a later accidental use into a clean crash instead of silent corruption.
Why is malloc(n * sizeof *p) better than malloc(n * sizeof(int))?
Because sizeof *p follows the pointer's type automatically. If p later becomes a long * or a struct Point *, the allocation size updates itself; spelling out sizeof(int) leaves a size that is now wrong and that the compiler will not flag.
What happens if you don't free memory in C?
The block stays allocated for the life of the process - a memory leak. A short program gets away with it because the operating system reclaims everything on exit, but a long-running program leaks a little on every pass and eventually exhausts memory. See memory leaks.