malloc, calloc, realloc, free
Pointers and memory13 min readLesson 4 of 6
4.1 Memory requested at run time
A local array has a fixed size chosen when the code compiles. When the size is only known while the program runs, ask the heap for memory with malloc.
malloc(n) returns a pointer to n bytes, or NULL when it cannot satisfy the request. The bytes are uninitialised, so reading before writing gives an arbitrary value.
<stdlib.h> declares malloc, calloc, realloc, and free. Library functions such as fopen allocate internally; the four here are the ones you call directly.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int count = 4;
int *values = malloc((size_t)count * sizeof *values);
if (values == NULL) {
printf("allocation failed\n");
return 1;
}
for (int i = 0; i < count; i++) {
values[i] = (i + 1) * 10;
}
printf("%d %d %d %d\n", values[0], values[1], values[2], values[3]);
free(values);
return 0;
}Output
10 20 30 40
4.2 sizeof the pointed-to type
The allocation size has to match the element type. malloc(count * sizeof *values) reads the type from the pointer itself, so a later change to the element type keeps the allocation correct.
sizeof *values is valid even though values has not been assigned yet, because sizeof examines the type without evaluating the expression. That idiom replaces sizeof(int), which breaks silently when the element type changes.
Leave the cast off the result. C converts void * to any object pointer type, and a cast only hides a missing declaration of malloc on very old compilers.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t count = 3;
double *samples = malloc(count * sizeof *samples);
if (samples == NULL) return 1;
samples[0] = 1.5;
samples[1] = 2.5;
samples[2] = 3.5;
printf("%.1f %.1f %.1f\n", samples[0], samples[1], samples[2]);
free(samples);
return 0;
}Output
1.5 2.5 3.5
4.3 calloc starts at zero
calloc(count, size) allocates space for count objects of size bytes and sets every byte to zero. The two arguments make the intent clearer than one multiplied expression.
Zeroed memory matters for arrays of counts, flags, and tables that are filled in later. All-zero bytes do not have to represent a null pointer by the standard, though mainstream platforms work that way, so check before relying on calloc for pointer arrays.
malloc plus memset does the same work. calloc states the goal once and can be faster when the system has zeroed pages ready.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *counts = calloc(4, sizeof *counts);
if (counts == NULL) return 1;
counts[2] += 5;
for (int i = 0; i < 4; i++) {
printf("%d%c", counts[i], i == 3 ? '\n' : ' ');
}
free(counts);
return 0;
}Output
0 0 5 0
4.4 Growing with realloc
realloc(ptr, new_size) asks for a block of a new size. It may return the same address, a different address, or NULL on failure.
The NULL case is the trap. Assigning the result straight back to the original pointer loses the old block when realloc fails, which leaks the memory and leaves nothing to free.
On success, the old contents are copied up to the smaller of the two sizes, and the original block is released. The old pointer must not be used afterwards.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *values = malloc(2 * sizeof *values);
if (values == NULL) return 1;
values[0] = 10;
values[1] = 20;
int *grown = realloc(values, 5 * sizeof *values);
if (grown == NULL) { free(values); return 1; }
values = grown;
for (int i = 2; i < 5; i++) values[i] = values[i - 1] + 10;
for (int i = 0; i < 5; i++) printf(i == 4 ? "%d\n" : "%d ", values[i]);
free(values);
return 0;
}Output
10 20 30 40 50
4.5 free gives memory back
free(ptr) releases a block from malloc, calloc, or realloc. The pointer must be one an allocation function returned, and it must not have been freed before.
Passing NULL is allowed and does nothing, which lets cleanup code run without branches. Passing an address inside a block, a stack address, or a stale pointer is undefined behaviour, and most allocators corrupt their bookkeeping or crash.
After free, set the pointer to NULL when it stays in scope. A stale pointer that still holds the old address is the start of a use-after-free bug.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *block = malloc(3 * sizeof *block);
if (block == NULL) return 1;
block[0] = 7;
printf("before free: %d\n", block[0]);
free(block);
block = NULL;
if (block == NULL) printf("pointer cleared\n");
free(block);
return 0;
}Output
before free: 7 pointer cleared
4.6 Who frees what
Every allocation needs an owner. The simplest rule gives ownership to the function that called malloc and frees the block in the same function, usually in the same block.
When a function returns allocated memory, the caller becomes the owner. Put that contract in the name or a comment: copy_text returns memory the caller must free. A function that takes ownership needs an equally clear name, such as consume.
Allocation order and free order do not have to match. Nesting is a convention for keeping the bookkeeping readable.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *copy_text(const char *text)
{
size_t size = strlen(text) + 1;
char *copy = malloc(size);
if (copy == NULL) return NULL;
memcpy(copy, text, size);
return copy;
}
int main(void)
{
char *label = copy_text("heap copy");
if (label == NULL) return 1;
printf("%s\n", label);
free(label);
return 0;
}Output
heap copy
4.7 Habits that prevent most heap bugs
- check every allocation before using the pointer
- size allocations with
sizeof *ptr - keep the result of
reallocin a temporary - free each block exactly once, and set the pointer to
NULLafterwards - run cleanup on the failure path as well as the success path
A function with several allocations and an early return can leak the earlier blocks. Because free(NULL) is safe, cleanup can run unconditionally once every pointer holds a valid address or NULL.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *first = malloc(4);
char *second = malloc(4);
if (first != NULL && second != NULL) {
first[0] = 'o'; first[1] = 'k'; first[2] = '\0';
second[0] = '!'; second[1] = '\0';
printf("%s%s\n", first, second);
}
free(first);
free(second);
return 0;
}Output
ok!
