Why this matters
Real programs rarely know their sizes in advance. A text editor does not know how long the file will be, a server does not know how many clients will connect, and a program reading numbers does not know how many will arrive. malloc and free are how C gets memory of any size, at run time, that lives as long as you need it. They are also the source of the most famous C bugs, so this lesson is precise about the rules.
The functions
Both are declared in <stdlib.h>:
void *malloc(size_t size);
void free(void *ptr);
malloc(size)reserves a block of at leastsizebytes on the heap and returns the address of its first byte. The contents are not initialized: they are whatever was there before. If the allocator cannot satisfy the request, it returnsNULL.free(ptr)gives a block back.ptrmust be exactly a value returned bymalloc(orcalloc/realloc) that has not already been freed.free(NULL)is allowed and does nothing.
Two types appear here that deserve a word:
size_tis the unsigned integer type used for sizes and counts of bytes. It is whatsizeofproduces, 8 bytes on 64-bit Linux, and printed with%zu.void *is a "pointer to anything": an address with no type attached. You cannot dereference it directly, but in C it converts automatically to any other object pointer type on assignment. Soint *p = malloc(...)needs no cast. (C++ requires a cast; C does not, and adding one can hide a missing#include <stdlib.h>in old compilers, so C style is to leave it out.)
A complete example
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n;
if (scanf("%d", &n) != 1 || n <= 0) {
printf("need a positive count\n");
return 1;
}
int *values = malloc(n * sizeof *values);
if (values == NULL) {
printf("out of memory\n");
return 1;
}
for (int i = 0; i < n; i++)
values[i] = i * 10;
int sum = 0;
for (int i = 0; i < n; i++)
sum += values[i];
printf("%d values, sum %d\n", n, sum);
free(values);
return 0;
}
$ echo 4 | ./prog
4 values, sum 60
Walking through the important lines:
malloc(n * sizeof *values)asks fornelements times the size of one element.sizeof *valuesmeans "the size of the thingvaluespoints to", heresizeof(int), 4. It does not evaluate*values(which would be invalid before the assignment);sizeofonly looks at the type. The idiom keeps working if you changeinttolonglater.- The NULL check comes immediately. On Linux small allocations practically never fail, but large ones can, and writing through a null pointer crashes. Checking is a habit worth having from day one.
values[i]works exactly as with an array, because array indexing is pointer arithmetic: the heap block is a contiguous run ofnints.free(values)returns the block. After this line,valuesstill holds the old address, but the memory no longer belongs to you.
What happens in memory:
stack (main's frame) heap
+----------------------+ +----+----+----+----+
| n = 4 | | 0 | 10 | 20 | 30 | 16 bytes from malloc
| values = 0x5a1c2a0 --+-------> +----+----+----+----+
| sum = 60 |
+----------------------+
after free(values):
+----------------------+ +-------------------+
| values = 0x5a1c2a0 --+-------> | (allocator's now) | dangling pointer
+----------------------+ +-------------------+
The pointer variable lives on the stack; the block it points to lives on the heap. Freeing the block does not touch the pointer, which becomes dangling. A common defensive habit is to set it to NULL right after freeing (free(values); values = NULL;), so that a mistaken later use crashes clearly instead of silently corrupting memory, and a second free becomes harmless.
How the allocator works, roughly
The allocator (glibc's malloc on Linux) keeps the heap as a collection of chunks. Each chunk has a small header just before the address you get, recording its size and whether it is in use. malloc searches its free lists for a chunk big enough (splitting a larger one if needed); free reads the header to learn the size and puts the chunk back on a free list, merging it with free neighbors. When it runs out, it asks the kernel for more address space.
[hdr| your 16 bytes ][hdr| someone else's 32 bytes ][hdr| free ...
^
pointer malloc returned
That is why free takes only a pointer: the size is in the header. It is also why heap bugs are so damaging: writing past the end of your block overwrites the next chunk's header, and the allocator may only notice much later, inside some unrelated malloc or free call, with a message such as malloc(): corrupted top size followed by Aborted.
Returning heap memory from a function
The heap solves the problem from the previous lesson: a function can create data that outlives its frame.
#include <stdio.h>
#include <stdlib.h>
/* Returns a new array of n squares. The caller must free it. */
int *make_squares(int n)
{
int *sq = malloc(n * sizeof *sq);
if (sq == NULL)
return NULL;
for (int i = 0; i < n; i++)
sq[i] = i * i;
return sq;
}
int main(void)
{
int *s = make_squares(5);
if (s == NULL)
return 1;
for (int i = 0; i < 5; i++)
printf("%d ", s[i]);
printf("\n");
free(s);
return 0;
}
0 1 4 9 16
The comment "The caller must free it" is part of the function's contract. C has no way to express ownership in the type system, so documentation and naming conventions carry it. You will see this pattern everywhere: the standard strdup function, for example, returns a malloced copy of a string that you must free.
Checking your work with valgrind
valgrind runs your program on a simulated CPU and watches every memory access and every allocation. At exit it reports what was never freed:
$ echo 4 | valgrind ./prog
==10== Memcheck, a memory error detector
==10== Command: ./prog
==10==
4 values, sum 60
==10==
==10== HEAP SUMMARY:
==10== in use at exit: 0 bytes in 0 blocks
==10== total heap usage: 3 allocs, 3 frees, 8,208 bytes allocated
==10==
==10== All heap blocks were freed -- no leaks are possible
==10==
==10== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
The number between == is the process id and varies. "3 allocs" when you only called malloc once? The C library allocated buffers for stdin and stdout on your behalf (and freed them at exit). Lesson 4 of this module covers valgrind's error reports in detail; the exercises already use it to check your code.
Common mistakes
- Forgetting
sizeof:malloc(n)givesnbytes, notnints. Writingnints into it overflows the block. valgrind reportsInvalid write of size 4. - Forgetting the terminator for strings: a copy of a 5-character string needs 6 bytes.
- Using memory after
free. It may appear to work. It is undefined behavior. - Freeing twice. glibc often detects it and aborts:
free(): double free detected in tcache 2 Aborted (core dumped) - Freeing something that did not come from
malloc, such as a local array or a pointer into the middle of a block:free(): invalid pointer, then abort. - Assuming new memory is zeroed. It is not; use
calloc(next lesson) or initialize it. - Losing the pointer (
p = malloc(...); p = malloc(...);). The first block can never be freed: a leak.
Recap
malloc(bytes)returns uninitialized heap memory orNULL; always check.- Size requests are in bytes:
malloc(n * sizeof *p). void *converts to any object pointer without a cast in C.free(p)releases exactly whatmallocreturned, once.free(NULL)is a no-op. Afterwardspdangles; set it toNULLif it stays in scope.- Functions may return heap memory; document that the caller must free it.
Going further: the malloc(3) manual page lists the whole family, including malloc_usable_size (the sandbox does not include the C library manual pages; read it with man 3 malloc on a system with the manpages-dev package, or at man7.org). Try valgrind ./prog on a program that forgets its free and read the "definitely lost" line.