malloc answers one question: give me this many bytes. Two companions in <stdlib.h> answer the questions that come next - give me this many bytes, cleared (calloc), and I need the block I already have to be bigger (realloc).
calloc: Count, Size, and Zeros
void *calloc(size_t count, size_t size);
Two arguments instead of one, and the result is filled with zero bytes.
That is the classic case: a histogram or tally array where every slot must start at zero. With malloc you would need a loop; calloc does it as part of the allocation, often for free because the operating system already hands out fresh pages pre-zeroed.
The overflow argument
The split into two arguments is not cosmetic. Consider a count read from a file:
size_t n = huge_value_from_input;
int *a = malloc(n * sizeof *a); /* the product can wrap around */
int *b = calloc(n, sizeof *b); /* required to detect the overflow */
If n * sizeof *a exceeds what size_t can hold, it wraps to a small number, malloc succeeds with a tiny block, and every subsequent write runs far past the end. calloc is required by the standard to fail and return NULL instead. When a size comes from outside your program, that check is worth having.
What "zeroed" actually means
calloc writes zero bytes. For integers and characters that is the value zero, which is what you want. For pointers and floating-point values, all-bits-zero is a null pointer and 0.0 on every mainstream platform, but the C standard does not promise it. Code that must be strictly portable assigns those explicitly.
calloc vs malloc + memset
int *a = calloc(n, sizeof *a);
int *b = malloc(n * sizeof *b);
if (b != NULL) {
memset(b, 0, n * sizeof *b);
}
They produce the same result. Prefer calloc: it is one line, it does the overflow check, and for large blocks it can avoid touching the memory at all. Prefer plain malloc when you are about to overwrite every byte anyway - zeroing a megabyte you are going to fill immediately is pure waste.
realloc: Changing the Size
void *realloc(void *p, size_t newsize);
realloc returns a block of newsize bytes whose contents match the old block up to the smaller of the two sizes. It may extend the block where it sits, or allocate a new one, copy, and free the old. You cannot tell which, so the only pointer you may use afterwards is the one it returned.
The tmp-pointer idiom
The three lines around tmp are the whole reason this function has a reputation. The tempting shorthand is broken:
/* BUG: leaks the original block when realloc fails */
p = realloc(p, newsize);
if (p == NULL) {
return;
}
When realloc fails it returns NULL and leaves the original block allocated and unchanged. Assigning the result straight into p overwrites the only pointer to that block, so it can never be freed - a leak, and one that happens exactly when memory is already scarce. Assign to tmp, test tmp, then commit:
void *tmp = realloc(p, newsize);
if (tmp == NULL) {
/* p is still valid; handle the failure however suits the caller */
return 0;
}
p = tmp;
Two more behaviors worth knowing: realloc(NULL, n) behaves exactly like malloc(n), which lets a growth function handle its first call without a special case. And growing leaves the new bytes uninitialized - only the old contents are preserved.
Growing an Array: The Standard Pattern
Here is the pattern almost every C container uses - a length, a capacity, and doubling when they meet.
Two design points carry the weight here.
Doubling, not adding one. Growing by a fixed amount makes each push copy the whole array, so filling n elements costs roughly n² byte copies. Doubling makes the copies rare enough that each push costs constant time on average. The cap == 0 ? 4 : cap * 2 start handles the first push without a separate branch, because realloc(NULL, n) is just malloc.
The struct owns one allocation. v.data is freed exactly once, by whoever owns the struct. Writing that ownership rule down - in a comment, or by pairing every vec_init with a vec_free - is what keeps a growing container from leaking as it moves between functions.
Shrinking
realloc with a smaller size trims a block, which is useful after you have read an unknown amount of data into an over-sized buffer:
A failed shrink is not an error worth aborting for: the old, bigger block is still valid and still correct. This is the one case where ignoring the failure is the right call - but note it still goes through tmp, because the rule about not overwriting the live pointer does not change.
Which One to Reach For
| You want | Use |
|---|---|
| Bytes, contents irrelevant | malloc(n * sizeof *p) |
| A count of items, cleared to zero | calloc(n, sizeof *p) |
| A count from untrusted input | calloc, for the overflow check |
| The block you have, bigger or smaller | realloc through a tmp pointer |
| First allocation in a growth loop | realloc(NULL, n) - no special case |
All four are released by the same free, exactly once.
Common Mistakes
p = realloc(p, n)- leaks on failure. Always go throughtmp.- Keeping an old pointer into the block. After
reallocthe data may have moved, so every saved interior pointer or index-derived address is stale. Indices survive a move; pointers do not. - Assuming the new bytes are zero. Only
calloczeroes.reallocleaves the growth region uninitialized. calloc(n * size, 1)- that throws away the overflow check the two-argument form exists for.- Freeing the old pointer after a successful
realloc.reallocalready released it; a secondfreeis a double free.
Frequently Asked Questions
What is the difference between calloc and malloc in C?
Two differences. calloc(n, size) takes the count and the element size separately and checks that multiplying them does not overflow, while malloc(n * size) can wrap silently. And calloc zero-fills the block, where malloc leaves it holding whatever was there before.
How does realloc work in C?
realloc(p, newsize) returns a block of the new size with your existing contents preserved up to the smaller of the two sizes. It may grow the block in place or move it, so the returned pointer is the only one you may keep using - the old one may already be freed.
Why should you assign realloc to a temporary pointer?
Because p = realloc(p, n) overwrites p with NULL when the call fails, and the original block is still allocated with nothing pointing at it - a leak. Assign to a tmp first, check it for NULL, and only then write p = tmp.
When should I use calloc instead of malloc?
When you genuinely need the memory zeroed (a counter array, a struct whose fields should start empty, a buffer you will fill partially) or when the size is a count times an element size that could be large. If you are going to overwrite every byte anyway, malloc skips the zeroing work.