Before you begin
Why this matters
Every block from malloc stays reserved until your code frees it. Forget a free, and the program uses more memory the longer it runs. Free a block twice, or use it after freeing it, and the program may crash far from the mistake, or seem to work. The compiler checks almost none of this, so the rules are yours to keep.
What this helps you reason about
- Say which part of a program must free each block, including a block that a function returns.
- Grow a block with realloc without losing it, and say which old pointers are no longer valid.
- Spot a leak, a double free, and a use after free when you read code.
In Java, Python, Go, and JavaScript, the runtime frees an object for you once nothing refers to it. C does not. Shallow and deep copying used malloc to give a copy its own block of memory and free to give the block back. This lesson is about the rules behind those two calls: who must call free, when, what calloc and realloc change, and what happens to every pointer when a block is freed or moved.
Already know this? The short version
- Ask for count * sizeof *p bytes and check the result for NULL. calloc sets every byte to zero; a block from malloc holds no reliable values until you write them.
- Each block has one owner, which frees it exactly once, after its last use. A missing free is a leak, a second free is a double free, and any use after free is undefined behavior; free(NULL) does nothing.
- realloc may move a block and free the old one: assign its result to a temporary, check for NULL, then replace the pointer, and never use a pointer into the old block.
- A function that returns a block hands ownership to its caller and says so in a comment. Free the blocks a struct owns before the struct itself.
- In real programs, watch for leaks on failing paths, a freed address kept in a second place, free on memory that malloc did not return, and struct copies that share blocks: see “What goes wrong with malloc and free in real programs?”.
Words in this lesson (8)
- malloc
- A function from <stdlib.h> that reserves a block of memory of a given number of bytes and returns its address, or NULL when no memory is left. The block lasts until free is called on it.
- free
- A function from <stdlib.h> that gives a block from malloc back. Each block must be freed exactly once, and not used afterwards.
- owner
- In C, the part of a program that is responsible for freeing a block: a variable, a struct member, or the caller of a function. C does not record the owner; comments and conventions say who it is.
- memory leak
- A block from malloc that is never freed, although the program will not use it again. In C it usually happens when the last pointer to the block is overwritten or goes away.
- dangling pointer
- A pointer to memory that no longer exists, such as a local of a call that has returned, or a block that was freed. Using it is undefined behavior.
- double free
- A second call to free on a block that was already freed. It is undefined behavior, and it often damages the records of the memory manager, so a later call crashes.
- calloc
- A function from <stdlib.h>, called as calloc(count, size), that reserves a block for count elements of size bytes each, sets every byte to zero, and returns its address, or NULL.
- realloc
- A function from <stdlib.h>, called as realloc(p, size), that changes the size of the block at p. It returns the address of the block, which may be new, or NULL when it fails; the old block is then unchanged.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / Ownership
Who gives a block back?
Shallow and deep copying used malloc(n) to reserve a block of n bytes and free(p) to give it back. malloc returns NULL, the null pointer, when it cannot reserve the block, so check the result before you use it.
C does not record who should free a block. The program decides. The owner of a block is the part of the program responsible for freeing it: a variable, a struct member, or the caller of a function. The rule is one owner per block, and one free per block, after the last use. Three mistakes break it:
- A memory leak: the block is never freed, although nothing will use it again. It stays reserved until the program ends.
- A double free: free is called a second time on the same block.
- A use after free: the program reads or writes a block after freeing it.
A double free and a use after free are undefined behavior. They often damage the records that the memory manager keeps, so the crash comes later, in a call that did nothing wrong.
After free(seeds), seeds still holds the old address. It is now a dangling pointer. Setting seeds = NULL after the free makes a later mistake easier to find: free(NULL) does nothing, and a read through NULL usually stops the program at once.
size_t count = 3;
int *seeds = malloc(count * sizeof *seeds);
if (seeds == NULL) {
/* no memory: nothing to free */
return 1;
}
seeds[0] = 40;
/* ... the last use of seeds ... */
free(seeds);
seeds = NULL; Every block from malloc has one owner, which frees it exactly once, after its last use.
Coming from Java: Java has no free: the garbage collector frees an object once nothing can reach it. In C, a block that nothing can reach is not freed. It is leaked.
Coming from Rust: Rust frees a value when its owner goes out of scope, and the compiler checks that there is one owner. C has the same rule with no checking: the owner is a convention you write down.
Precisely: C11 7.22.3 and 6.2.4
C11 7.22.3p1: the lifetime of an allocated object lasts from its allocation until its deallocation. 7.22.3.3p2: free(NULL) does nothing, and calling free on a pointer that did not come from malloc, calloc, realloc, or aligned_alloc, or on one already freed, is undefined behavior. 6.2.4p2: after the free, every pointer to the block has an indeterminate value, so even comparing seeds with another pointer is undefined, not only reading through it. A leak is not undefined behavior: the standard never requires free, and the operating system takes a process’s memory back when it ends. gcc 12 warns about the simplest cases at -Wall. free(p); followed by p[0] = 'x'; gives “pointer ‘p’ used after ‘free’ [-Wuse-after-free]”, and two free(p); calls in a row give the same warning. Do not rely on it: the warning comes from what gcc can see in one function, and an address copied into a struct or freed in another function can escape it.
Experiment
Free a block and look at the pointer
free gives the block back to the memory manager, but it cannot change the variable seeds, which holds a copy of the address. So seeds becomes a dangling pointer. The assignment seeds = NULL; replaces the address, so nothing in main still leads to the freed block.
Why it matters After free, a pointer still looks valid in the debugger and in the code. Only your own rule, one free after the last use, keeps the program from using it.
Predict what prints, and what seeds holds after free(seeds) runs. Then add the line seeds = NULL; right after free(seeds); and predict what seeds shows at the last stop.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
size_t count = 3;
int *seeds = malloc(count * sizeof *seeds);
if (seeds == NULL) {
return 1;
}
seeds[0] = 40;
seeds[1] = 25;
seeds[2] = 60;
printf("%d\n", seeds[0] + seeds[1] + seeds[2]);
free(seeds);
return 0;
}
What to look for
seeds appears in main’s frame at line 8, after malloc returns. The stepper shows a pointer as its address, then → and the first element it points to: 0x… → 0. This build’s new block happened to hold zeros; C does not promise that. After lines 11 to 13, seeds shows → 40, and the run prints 125. At line 16, after free(seeds), seeds still shows the same address, but → shows a large negative number such as -1431458804. The memory manager reused the freed bytes for its own records. Only the debugger may look at them; the program must not. After the edit, seeds shows 0x0 at the last stop.
Expected output
12502 / Size and contents
How many bytes should you ask for?
malloc counts bytes, not elements. For count elements, ask for count * sizeof *p bytes. sizeof *p is the size of one element that p points to, worked out from the type of p, so the line stays right if that type changes later. sizeof p, without the *, is the size of the pointer itself: 8 bytes in the stepper’s build, whatever it points to. C pointer arithmetic and bounds showed a related mistake: malloc(strlen(s)) leaves no byte for the '\0'.
malloc does not clear the block. Its contents are indeterminate: they can be anything, and reading them before writing them is a bug. calloc(count, size) reserves count elements of size bytes each and sets every byte to zero. Use calloc when the elements must start at zero, and malloc when the code writes every element before it reads one.
struct Plot {
double width;
double length;
int rows;
};
/* 4 Plots: 96 bytes in this build */
struct Plot *plots = malloc(4 * sizeof *plots);
/* bug: 4 pointers, only 32 bytes */
struct Plot *wrong = malloc(4 * sizeof plots);
/* 4 ints, every one 0 */
int *counts = calloc(4, sizeof *counts); Ask for count * sizeof *p bytes, and use calloc when the elements must start at zero.
Coming from Go: make and new in Go always return zeroed memory. In C, only calloc does; a block from malloc must be written before it is read.
Precisely: C11 7.22.3.2, 7.22.3.4, and overflow
7.22.3.4p2: the block from malloc has indeterminate contents. 7.22.3.2p2: calloc sets all bits to zero. For integer types that is 0. A double or a pointer with all bits zero is 0.0 or NULL on every common platform, but C does not promise it. The multiplication in malloc(count * sizeof *p) is done in size_t and wraps around when it overflows, so a huge count can give a small block. calloc does the multiplication itself, and glibc’s calloc returns NULL when it would overflow; the experiment in “What goes wrong with malloc and free in real programs?” shows this. malloc(0) may return NULL or a unique pointer (7.22.3p1); glibc returns a pointer, and it must be freed. The size of struct Plot is 24 in the stepper’s build: 8 + 8 + 4, and 4 bytes of padding so that each double in an array stays aligned.
03 / realloc
What happens to old pointers when a block grows?
realloc(p, size) changes the size of the block at p. If the block can grow where it is, realloc returns the same address. If not, it reserves a new block, copies the old contents, frees the old block, and returns the new address. Either way, use the returned address from then on. Every other pointer into the old block, such as a pointer to one element, may now be a dangling pointer.
realloc can also fail. Then it returns NULL and leaves the old block as it was. So the common line bulbs = realloc(bulbs, size); is a bug: when realloc fails, it overwrites the only pointer to the old block with NULL, and the block leaks. Assign the result to a temporary pointer, check it, and only then replace bulbs.
realloc(NULL, size) works like malloc(size). A list that starts empty, with its pointer set to NULL, can grow with the same code.
int *grown = realloc(bulbs, 100 * sizeof *bulbs);
if (grown == NULL) {
/* bulbs is unchanged, and still owned here */
free(bulbs);
return 1;
}
bulbs = grown;
/* pointers into the old block are no longer valid */ Assign realloc’s result to a temporary, check it, then replace the pointer; never use a pointer into the old block.
Coming from Go: append may also return a slice that uses a new array. In Go the old array stays valid while anything still uses it; in C, realloc frees the old block at once.
Precisely: C11 7.22.3.5
7.22.3.5p2–4: realloc deallocates the old object and returns a pointer to a new one, with the same contents up to the smaller of the two sizes; the new pointer may equal the old one. If no memory can be reserved, the old object is not deallocated and keeps its value. realloc(p, 0) is implementation-defined in C11, obsolescent in C17, and undefined behavior in C23; call free(p) instead. Whether a block moves is decided by the memory manager (glibc in the stepper), which grows a block in place when the memory after it is free and rounds small blocks up. Code must assume a move every time.
Experiment
Grow a block that has no room after it
realloc keeps a block where it is when the memory after it is large enough, and otherwise reserves a new block, copies the contents, and frees the old one. 100 ints did not fit before seeds, so the block moved. The memory manager rounds small blocks up, so 4 ints fit in place.
Why it matters Whether a block moves depends on what else the program has reserved, so code that works with one size can break with another. Always use the address realloc returns, and never a pointer into the old block.
bulbs holds 3 ints, and seeds is reserved right after it. Predict whether grown gets the same address as bulbs when bulbs grows to 100 ints, and what bulbs shows before line 20 runs. Then change 100 to 4 and predict again.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
int *bulbs = malloc(3 * sizeof *bulbs);
int *seeds = malloc(3 * sizeof *seeds);
if (bulbs == NULL || seeds == NULL) {
free(bulbs);
free(seeds);
return 1;
}
bulbs[0] = 12;
int *grown = realloc(bulbs, 100 * sizeof *bulbs);
if (grown == NULL) {
free(bulbs);
free(seeds);
return 1;
}
bulbs = grown;
printf("%d\n", bulbs[0]);
free(bulbs);
free(seeds);
return 0;
}
What to look for
Compare the last three digits of the addresses. In this build, seeds starts 0x20 bytes after bulbs: the memory right after bulbs is taken. At line 15, grown shows a new address, further on, with → 12: realloc copied the old contents. bulbs still shows the old address, and its → now shows leftover bytes, because realloc freed the old block. At line 21, after bulbs = grown, bulbs and grown show the same new address, and the run prints 12. After the edit, grown shows the same address as bulbs, and bulbs still shows → 12: 4 ints fit in the space this build had already reserved for 3.
Expected output
1204 / Returning a block
Who frees memory that a function returns?
A function can return a block from malloc. The block outlives the call: it lasts until free, not until the function returns. The caller receives the only pointer to it, so the caller becomes the owner. This is a transfer of ownership, and C has no syntax for it. Write it in a comment above the function: “The caller owns the label and must free it.”
The opposite case exists too. getenv returns a pointer to memory that the library keeps, and the caller must not free it. Only the documentation tells the two cases apart, so read it before you free a result.
A leak can look harmless in the code. If the only pointer to a block gets a new address, nothing holds the old address any more, and nothing can free that block.
/* Returns a new label, such as "mint, row 2".
The caller owns the label and must free it. */
char *label_for(const char *plant, int row);
char *label = label_for("mint", 2);
/* ... use label ... */
free(label); A function that returns a block hands ownership to its caller; say so in a comment above the function.
Coming from Python: A Python function can return a new list, and the caller never thinks about who frees it. In C the caller of label_for has a new job: one free for every call.
Precisely: strdup and other functions that allocate
POSIX strdup returns a new copy of a string that the caller must free. It is not part of C11, and the stepper builds with -std=c11, so a call to it fails: “implicit declaration of function ‘strdup’”. C23 adds strdup to <string.h>. Until then, a copy is malloc(strlen(s) + 1) and strcpy. The C library has both kinds of function: getenv, strerror, and asctime return memory the library owns and may reuse at the next call, while a pointer from malloc, calloc, or realloc is yours.
Experiment
Hand a label to the caller
The block from malloc does not belong to label_for’s frame, so it stays when the call returns. The returned address is the only pointer to it, so main becomes its owner. Assigning a second result to label overwrote the only copy of the first block’s address.
Why it matters When a function returns a block, store the result in a variable that will be freed, and free the old block before you reuse the variable.
Predict which address main’s label holds after label_for returns. Then add the line label = label_for("sage", 3); right after the printf line, and predict what happens to the block that holds "mint, row 2".
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Returns a new label, such as "mint, row 2".
// The caller owns the label and must free it.
char *label_for(const char *plant, int row) {
size_t size = strlen(plant) + 16;
char *label = malloc(size);
if (label != NULL) {
snprintf(label, size, "%s, row %d", plant, row);
}
return label;
}
int main(void) {
setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
char *label = label_for("mint", 2);
if (label == NULL) {
return 1;
}
printf("%s\n", label);
free(label);
return 0;
}
What to look for
Step into label_for. After line 9, its label shows an address and "", then, after snprintf, "mint, row 2". At line 19, label_for’s frame is gone, and main’s label shows the same address and text. The run prints mint, row 2. After free(label), label shows leftover bytes. After the edit, the second call’s label gets a different address, and from line 24 main’s label shows that address with "sage, row 3". No variable holds the first block’s address any more, so nothing can free it: it has leaked. The output stays mint, row 2.
Expected output
mint, row 205 / Nested blocks
How do you free a struct that owns other blocks?
A struct can own other blocks through its pointer members. free(bed) gives back the struct’s own block only. The blocks that its members point to stay reserved. Once the struct is freed, their addresses can no longer be read from it, so they leak.
So free from the inside out. Free each block that a member owns, then the struct. Put these steps in one function next to the function that makes the struct, so every owner frees it the same way.
The order is the rule. In the other order, free(bed) comes first, and bed->name then reads the freed block: a use after free. gcc 12 warns about this case: “pointer ‘bed’ used after ‘free’ [-Wuse-after-free]”, once for each member read after the free.
struct Bed {
char *name;
int *rows;
};
/* Frees the bed and the two blocks it owns. */
void free_bed(struct Bed *bed) {
if (bed == NULL) {
return;
}
free(bed->name);
free(bed->rows);
free(bed);
} Free the blocks a struct owns first, then the struct itself.
Coming from Java: In Java, dropping the last reference to an object also releases everything only it refers to. In C, free(bed) releases one block, and each member’s block needs its own free.
Precisely: members a struct does not own
A struct can also hold pointers it does not own, such as a pointer to a shared price list or to a string literal. Write in a comment which members the struct owns, and free only those. Accepting NULL, as free does, lets cleanup code call free_bed on a bed that was never made. A C++ destructor runs these steps automatically when an object ends; in C, every owner calls free_bed itself, on every path.
06 / In production
What goes wrong with malloc and free in real programs?
The rules in this lesson explain four bugs that C programmers meet in real programs.
A failing path leaks. A function makes a struct, then a second allocation fails, and the function returns NULL without freeing the first block. The experiment below shows this. Before such a return, free everything made so far, as Cleanup on a failing path showed.
A second pointer outlives the block. A list frees an item, but a “selected item” pointer, a cache, or another list still holds its address, and its next use reads freed memory. When you free a block, find every place that holds its address. Where a second place only needs to find the item again, store a position or an id instead of a pointer.
free gets memory that malloc did not return: a string literal, a local array, or a pointer that was moved with p++. gcc warns when it can see the source, for example “‘free’ called on a pointer to an unallocated object ‘"mint"’ [-Wfree-nonheap-object]”. A member that sometimes points to a literal and sometimes to a block cannot be freed safely; make it always own a block.
A struct copy shares its blocks. struct Bed copy = *bed; copies the name and rows pointers, not the blocks, so freeing both beds frees each inner block twice. Give the copy its own blocks, as Shallow and deep copying showed.
char *text = malloc(16);
if (text == NULL) {
return 1;
}
/* walk with a second pointer */
char *p = text;
p++;
/* free the address that malloc returned */
free(text); Free what a function made on every path, find every copy of a freed address, and free only addresses that malloc returned.
Precisely: tools that find these bugs
gcc and clang build with -fsanitize=address to check every heap access at run time: the program stops with a report at the first use after free or double free, and on Linux its leak checker lists every block still reserved at exit, with the call that made it. Valgrind’s Memcheck finds the same bugs in a program built without changes, more slowly. The stepper builds with fixed options and no sanitizer, so this lesson describes these reports instead of showing them. Both tools find only the bugs that a run reaches, which is why failing paths need tests that make an allocation fail.
Experiment
Run out of memory halfway through making a bed
calloc found that count * sizeof *bed->rows does not fit in a size_t and returned NULL instead of a block. The function had already reserved bed, and the early return dropped the only pointer to it.
Why it matters A failing path is code that tests rarely run, so its leaks stay hidden. Before a function returns NULL, free every block it has made so far.
SIZE_MAX, from <stdint.h>, is the largest size_t value, so main asks for far more rows than any machine has. Predict what prints, and what happens to the block from the first malloc. Then add the line free(bed); just before the second return NULL; and predict again.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
struct Bed {
size_t count;
int *rows;
};
// Returns a new bed, or NULL when no memory is left.
// The caller owns the bed and its rows.
struct Bed *make_bed(size_t count) {
struct Bed *bed = malloc(sizeof *bed);
if (bed == NULL) {
return NULL;
}
bed->count = count;
bed->rows = calloc(count, sizeof *bed->rows);
if (bed->rows == NULL) {
return NULL;
}
return bed;
}
int main(void) {
setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
struct Bed *bed = make_bed(SIZE_MAX / 2);
printf("%s\n", bed == NULL ? "no bed" : "bed ready");
return 0;
}
What to look for
Step into make_bed. After line 13, bed shows an address and → {count: 0, rows: 0x0}: the stepper shows the struct that bed points to. After line 17, count is 9223372036854775807. After line 18, rows is still 0x0: calloc returned NULL. make_bed returns NULL at line 20, and at line 28 main’s bed shows 0x0. No variable holds the address that make_bed’s bed held, so that block has leaked. The run prints no bed. After the edit, the run stops at free(bed); and the next stop shows leftover bytes in the freed struct, such as a very large count. The output is the same: only the free shows the difference.
Expected output
no bed07 / Try it
Can you count the frees?
join returns a new block. Work out its size and who owns it before you read main.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Returns "a b" in a new block. The caller owns it.
char *join(const char *a, const char *b) {
size_t size = strlen(a) + strlen(b) + 2;
char *text = malloc(size);
if (text != NULL) {
snprintf(text, size, "%s %s", a, b);
}
return text;
}
int main(void) {
char *pair = join("sweet", "pea");
if (pair == NULL) {
return 1;
}
char *same = pair;
printf("%s %zu\n", same, strlen(pair));
free(pair);
return 0;
}
Precisely: why the + 2 matters
snprintf never writes more than size bytes, so a size one too small would cut the last character, not overflow the block. With strcpy and strcat instead of snprintf, the same mistake would write the '\0' one byte past the block, which is undefined behavior and is the kind of write -fsanitize=address reports.
What does it print? How many bytes does join reserve? After free(pair), may the program call free(same)?
Reveal the reasoning
sweet pea 9. join reserves 5 + 3 + 2 = 10 bytes: one more for the space and one for the '\0'. The string has 9 characters. main owns the block because join hands it over, and the comment says so. same is a second pointer to the same block, not a second block, so the program needs exactly one free. After free(pair), both pair and same are dangling pointers, and free(same) would be a double free.
Practice tickets
- C-701 · The seed swap sign shows 0 g as the lightest packet → Free
- C-702 · The tag printer’s memory grows with every batch → Sign in · Pro
- C-703 · Shortening a bed’s name leaves the sign blank → Sign in · Pro
Keep exploring
Primary references
- C11 draft standard (N1570): 7.22.3 Memory management functions, 6.2.4 Storage durations of objects ↗
- cppreference: malloc ↗
- cppreference: calloc ↗
- cppreference: realloc ↗
- cppreference: free ↗
- GCC manual: Warning Options (-Wuse-after-free) ↗
- GCC manual: Program Instrumentation Options (-fsanitize=address) ↗