Memory you ask for
Fixed arrays have a fixed size. malloc asks the system for memory while the program runs, and free hands it back when you are done.
The limit of fixed arrays
An array declared in a function has a size fixed when you write the program. If you do not know how many values you need until the program is running, a fixed array is either too small or wastes space.
The fix is the heap, a pool of memory the program can ask for at run time. You request a block, use it, and give it back. The size is a number you compute, not a number baked into the code.
malloc and free
malloc(bytes) asks the system for that many bytes and hands back a pointer to the start of the block. To ask for room for n ints, multiply n by the size of one int and pass that to malloc.
The pointer it returns works like an array: values[0], values[i], all of it. When you are done, free(values) returns the block. Every malloc wants a matching free.
When there is no room
A request can fail, and then malloc hands back NULL, a pointer that points nowhere. A careful program checks for it before using the pointer, because following a NULL pointer crashes.
Two habits keep heap code healthy: check the pointer after malloc, and free exactly once when you are finished. Forgetting the free leaks the memory until the program ends.
Stack and heap are different promises
A local variable lives on the stack and disappears when its function returns. A block from malloc lives on the heap until you free it, no matter which function asked for it or which one returns. That is what lets a list outlive the call that built it.
malloc hands back raw bytes with no initial value, and reading them before writing is undefined. calloc(count, size) does the same job but zeroes the block first, which is why it takes two arguments and why it is the safer start for an array of numbers.
Ownership, growth and the way out
Every block needs one owner — the piece of code responsible for freeing it. Passing the pointer to a function that only reads it is a loan, not a transfer. Two owners and you get a double free; no owner and you get a leak.
realloc resizes a block, but it may move it, so assign the result to a temporary first. If it returns NULL the old block is still alive, and overwriting your only pointer with NULL loses it for good.
The idiom that survives a type change
The usual request is malloc(count * sizeof *values), not malloc(count * sizeof(int)). Both work today, but the second repeats the type: change values to a double * and the allocation still asks for ints, which compiles and quietly under-allocates. The sizeof *values form follows the pointer’s own type, so it stays correct.
The multiplication can overflow before malloc ever sees it. If count is huge, count * sizeof *values may wrap to a small number and the request succeeds with far too little room. Check the count against a limit first, or use calloc, which takes the count and the size separately and can check the product before it allocates.
Every exit path has to free
A function that allocates and then returns early on an error has to free before it goes. The easy mistake is a check in the middle: the happy path frees at the end, the error path returns straight out, and the block is lost. Every path out of the function needs to pass the free, or the allocation should belong to the caller.
One shape that keeps this honest is a single cleanup label at the bottom, with the error branches setting a result and jumping to it. The block is freed once, after the work, whatever happened. The other shape is to allocate in the caller and pass the block in, so the function never owns it at all.
Three heap mistakes do lasting damage: asking for malloc(count) bytes when you meant count * sizeof(int), writing p = realloc(p, ...) so a failed resize loses the original block, and freeing the same block twice. Multiply by the element size, keep the old pointer until the new one is confirmed, and set the pointer to NULL after the free.
Try these next
- Ask for a block with
calloc, print every slot before writing, then fill it and print again. - Grow a block from three ints to six with
realloc, keeping the old values. - Write a function that frees a block and sets the caller's pointer to NULL; explain why the parameter has to be an
int **.
Ask and release
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *values = malloc(3 * sizeof(int));
if (values == NULL) {
printf("No memory\n");
return 1;
}
values[0] = 10;
printf("First %d\n", values[0]);
free(values);
return 0;
}A dynamic array of four
Run it, then change the count to 6 and follow the loops, which already know the new size. Every value is filled and read back through the pointer.
Zeroed memory, then a resize
Run it to see the zeros `calloc` supplies, then change `count` to 6 and `grown_count` to 8. Fill each new slot before the final print.
Using memory after you free it is worse than leaking it: the block may already belong to something else. Free once, at the end, and do not touch the pointer again.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does malloc return?
