C Part I · Module 9 · Memory and Object Lifetime
malloc and free
The previous chapter named three walls that automatic and static objects cannot get past, and said the third storage duration removes all of them. Here it is, and the striking thing about it is how little is new. The function hands back a pointer, you use it exactly as you have used pointers since Module 7, and you hand it back when you are finished. Every piece of syntax is something you already have — the return type is the generic pointer from Chapter 7.12, the failure value is the null pointer from Chapter 7.4, and the result is indexed by the rules from Chapter 7.8. What is genuinely new is an obligation: for the first time in this curriculum, an object's lifetime does not end on its own, and three habits have to become automatic before you write anything that depends on it.
Intermediate18 min readCPart IFundamentalsMemory
Part I · Module 9 · Chapter 9.6 · malloc and free
1. Two Functions
#include <stdlib.h>
void *malloc(size_t size);
void free(void *pointer);malloc asks for size bytes and returns the address of a block that large, or NULL if it cannot provide one.
free ends the lifetime of a block obtained that way.
Both come from <stdlib.h>. Without the include, the calls are an error — Chapter 5.5's rule, and a useful one here.
Read the signatures against Module 7 and almost everything is already familiar:
| Part of the signature | What you already know |
|---|---|
returns void * | Chapter 7.12 — an address with the type removed, convertible without a cast |
returns NULL on failure | Chapter 7.4 — the deliberate "no object" value |
takes a size_t | Chapter 2.12 — the type for sizes and counts |
free takes a void * | any object pointer converts to it implicitly |
The new thing is in neither signature. It is that the lifetime of the block begins at the malloc and ends at the free, and nothing else ends it.
2. The Smallest Complete Example
#include <stdio.h>
#include <stdlib.h>
static int static_object = 1;
int main(void)
{
int automatic_object = 0;
int *allocated = malloc(sizeof *allocated);
if (allocated == NULL) {
printf("allocation failed\n");
return 1;
}
*allocated = 42;
printf("static object %p\n", (void *)&static_object);
printf("automatic object %p\n", (void *)&automatic_object);
printf("allocated object %p holds %d\n", (void *)allocated, *allocated);
free(allocated);
return 0;
}One run:
static object 0x10097c000
automatic object 0x16f48a778
allocated object 0x1010b9a00 holds 42Chapter 9.4 §8 asked you to predict where the third line would land. The answer is a third region — near neither the static object nor the automatic one. That is the heap, and it is the first time in this curriculum that an object has appeared there.
Everything after the allocation is ordinary. *allocated = 42; is Chapter 7.2's write through a pointer. allocated is an int * like any other, and nothing about using it differs from a pointer to a local.
3. Obligation One: Check Every Request
malloc can fail, and when it does it returns NULL. This is not a theoretical possibility you check for ceremonially:
void *huge = malloc((size_t)-1);
printf("returned NULL ? %d\n", huge == NULL);returned NULL ? 1A request for the largest representable size is refused, and the refusal is reported as a value you can test.
Check the result of every allocation before using it. Chapter 7.4 §5's table named this exact case — a pointer from an operation that is allowed to fail — and said it must be checked at the boundary, once.
The shape is Chapter 5.4's early return:
int *values = malloc(count * sizeof *values);
if (values == NULL) {
return -1; /* or whatever failure means here */
}
/* from here on, values is known good */Skipping the check does not produce a clean failure. It produces a null dereference the moment you write through the pointer, which is Chapter 7.4 §3's undefined behaviour — and Chapter 7.4 §4 established that nothing warns you.
4. Obligation Two: Ask for the Right Number of Bytes
malloc takes bytes, not elements. For an array of count items:
int *readings = malloc(count * sizeof *readings);Two spellings are common and one is better:
double *a = malloc(10 * sizeof *a); /* preferred */
double *b = malloc(10 * sizeof(double)); /* repeats the type */Both work. sizeof *a cannot disagree with the declaration — change double *a to float *a and the first line follows automatically while the second silently allocates twice what is needed.
This is the same argument Chapter 6.1 made for sizeof arr / sizeof arr[0] over a literal count, and Chapter 6.2 made for int a[] = {...} over a hand-written size: derive the number from the thing it describes rather than restating it. Note also that sizeof *a does not dereference anything — Chapter 2.12 established that sizeof does not evaluate its operand, so writing it before the pointer is valid is fine.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t wanted = 7; /* imagine this arrived with the data */
int *readings = malloc(wanted * sizeof *readings);
if (readings == NULL) {
return 1;
}
for (size_t i = 0; i < wanted; i++) {
readings[i] = (int)(i * i);
}
printf("%zu elements:", wanted);
for (size_t i = 0; i < wanted; i++) {
printf(" %d", readings[i]);
}
printf("\n");
free(readings);
return 0;
}Output:
7 elements: 0 1 4 9 16 25 36This is Chapter 9.5's first wall removed. wanted is an ordinary run-time value; nothing about it has to be known while compiling.
Note what the loop looks like: readings[i] on a pointer, bounded by a count carried alongside. That is Chapter 7.7's pointer-plus-length discipline, and it is now mandatory rather than merely advisable — there is no array object here at all, so sizeof readings is a pointer's size and the count exists only because you kept it.
5. Obligation Three: Release It Exactly Once
free(readings);free ends the block's lifetime. After it, Chapter 9.1's rule applies in full: every use of that pointer is undefined.
Three properties are worth having precisely.
free(NULL) is defined and does nothing. Verified. This is deliberately useful — a cleanup path can call free on a pointer that may never have been set, provided it was initialised to NULL, without a conditional.
The pointer passed must be one a previous allocation returned, unchanged. Passing a pointer to a local, or one that has been advanced with pointer arithmetic, is undefined.
Exactly once. Not zero times — that is Chapter 9.9's leak. Not twice — that is Chapter 9.10's double-free. The pairing is a property of the program, not of any single function, which is why Chapter 9.8 is about designing interfaces that make it obvious who is responsible.
6. Two Details Worth Knowing Now
You do not need a cast.
int *p = malloc(n * sizeof *p); /* correct C */
int *p = (int *)malloc(n * sizeof *p); /* also compiles */Chapter 7.12 §2 established that a void * converts to any object pointer type implicitly in C, and that the cast you see everywhere is a C++ compatibility habit. It also gave the reason not to add it reflexively: a cast silences diagnostics, so a stray one can hide a genuine mismatch. Write the first form in C.
A fresh block is not zeroed.
The bytes malloc returns hold indeterminate values — not zero, and not anything you may rely on. This is the same situation Chapter 2.3 described for an uninitialised local, and the full account of what indeterminate actually means is Chapter 9.12's, because the answer is more subtle than "a random number".
Until then, the practical rule is Chapter 6.2's applied here: initialise it before you read it. Chapter 9.7 introduces a function that allocates and zeroes in one step, for when that is what you want.
malloc(0) is implementation-defined. It may return NULL, or a non-null pointer that must not be dereferenced. On this implementation it returned non-null. Either way, nothing useful can be read or written through the result, so the sensible position is to avoid asking for zero bytes rather than to learn which answer your platform gives.
7. A Complete Example
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t count = 5;
int *samples = malloc(count * sizeof *samples);
if (samples == NULL) {
printf("could not allocate %zu samples\n", count);
return 1;
}
for (size_t i = 0; i < count; i++) {
samples[i] = (int)(10 * (i + 1));
}
int total = 0;
for (size_t i = 0; i < count; i++) {
total = total + samples[i];
}
printf("samples:");
for (size_t i = 0; i < count; i++) {
printf(" %d", samples[i]);
}
printf("\ntotal: %d\n", total);
free(samples);
samples = NULL;
return 0;
}Output:
samples: 10 20 30 40 50
total: 150Five things in that program are the pattern to copy.
The size comes from sizeof *samples, so it tracks the declaration.
The result is checked immediately, before any use, and the failure path returns rather than continuing.
The count travels with the pointer through every loop, because there is no array to ask.
free is called exactly once, on the pointer the allocation returned, unmodified.
samples = NULL; after the free is a habit worth adopting now and understanding fully in Chapter 9.11. It costs one assignment and converts a particular class of later mistake from undefined behaviour into a null dereference — which is not good, but is far more likely to be noticed.
8. What You Should Be Able to Say Now
mallocreturns a block of the requested size, orNULL;freeends its lifetime. Both from<stdlib.h>.- The lifetime begins at the
mallocand ends at thefree— Chapter 9.1's allocated duration. - The block lands in a third region, near neither the statics nor the automatics.
- The return type is
void *— Chapter 7.12 — so no cast is needed in C. - Check every allocation before using it.
malloc((size_t)-1)returnsNULL, verified. malloctakes bytes. Usecount * sizeof *p, which cannot disagree with the declaration.- The count must travel with the pointer — there is no array to ask, so Chapter 7.7's pairing is mandatory.
freeexactly once, on the unmodified pointer the allocation returned.free(NULL)is defined and does nothing.- A fresh block is not zeroed;
malloc(0)is implementation-defined. - Nothing releases it but you, on every path — which is what the next five chapters are about.
9. Exercises
Exercise 1 — write the allocation. For each, write the malloc call and the check, using the sizeof *p form.
- 12
intobjects - one
double nobjects of typeunsigned char, wherenis asize_tvariable
Exercise 2 — find the sizing bug. Each of these asks for the wrong number of bytes. Say what it actually allocates and give the correction.
int *a = malloc(10);
double *b = malloc(10 * sizeof(int));
int *c = malloc(sizeof c);
char *d = malloc(n); /* n characters plus a terminator */Exercise 3 — predict the output. Do not compile yet, and say which line would be undefined if the check were removed and the allocation failed.
int *p = malloc(3 * sizeof *p);
if (p == NULL) { return 1; }
p[0] = 1; p[1] = 2; p[2] = 3;
printf("%d %d %d\n", p[0], p[1], p[2]);
free(p);Exercise 4 — classify four claims. Say whether each is an ISO C guarantee, implementation-defined, or false.
mallocreturns zeroed memoryfree(NULL)is safemalloc(0)returnsNULL- a cast is required when assigning
malloc's result in C
Exercise 5 — repair the function. This has three faults. Name all three and give a corrected version.
int *make_buffer(int n)
{
int *b = (int *)malloc(n);
for (int i = 0; i < n; i++) { b[i] = 0; }
return b;
}Exercise 6 — count the paths. A function allocates a buffer and has three return statements. Say how many free calls the function needs in the worst case, what happens if one path is missed, and which chapter covers that failure.
Exercise 7 — explain the third region. §2's output shows three addresses in three places. Say which storage duration each corresponds to, and whether the clustering is a C guarantee.
Exercise 8 — justify the habit. samples = NULL; after the free changes nothing about the block. Explain what it does change, what class of later mistake it converts, and why the result is still a bug rather than a fix.
10. Summary
mallocandfreegive you allocated storage duration — a lifetime you begin and end.- The return is
void *, needing no cast in C; failure isNULL. - The block lands in a third region, which is Chapter 9.4's heap.
- Check every request before using it — the refusal is a value, not a crash.
- Ask in bytes:
count * sizeof *p, so the size tracks the declaration. - Carry the count — there is no array object to ask.
- Free exactly once, on the unmodified returned pointer;
free(NULL)does nothing. - A fresh block is not zeroed;
malloc(0)is implementation-defined. - Nothing releases it but you, on every path — the subject of Chapters 9.8 to 9.12.
One obligation in §4 came with an awkward consequence: a fresh block holds nothing you can rely on, so an allocation intended to start from zero needs a loop immediately after it. That is common enough that the standard library provides a function which does both in one call. There is also a harder problem lurking in §5's fixed count — a block's size is decided when you ask for it, and a program that discovers it needs more has no way yet to say so without losing what it already has. The next chapter covers both functions, and the second one has a failure mode that destroys your data if you call it in the obvious way.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
