C Part I · Module 9 · Memory and Object Lifetime
Why Dynamic Allocation Exists
The third storage duration has been named in every chapter of this module and used in none of them. Before the mechanics arrive it is worth being precise about what it is for, because allocation is routinely treated as the sophisticated way to make objects and it is not — it is the answer to three specific problems, it brings an entire category of bug that the other two durations cannot produce, and the second half of this module exists because of that. Every example in this curriculum since Module 6 has quietly worked around the limits described here: arrays sized for the worst case imaginable, buffers supplied by callers, functions that cannot produce a result larger than something somebody else already set aside. This chapter names the three walls you eventually hit, and gives you a test for deciding whether you have actually hit one.
Intermediate16 min readCPart IFundamentalsMemory
Part I · Module 9 · Chapter 9.5 · Why Dynamic Allocation Exists
1. What You Can Already Do
It is worth starting with how much is already covered, because the honest answer is most things.
| Need | Solved by | Since |
|---|---|---|
| A value that lives inside one function | an automatic object | Chapter 9.2 |
| A fixed-size collection | an array | Chapter 6.1 |
| A result handed back to a caller | returning it by value | Chapter 5.4 |
| Filling storage the caller owns | a pointer-plus-capacity parameter | Chapters 7.5, 7.7 |
| State that is genuinely one per program | a static object | Chapter 9.3 |
Between them those cover the overwhelming majority of what programs do. Reaching past them should feel like a decision, not a default.
Three walls remain.
2. The First Wall: the Size Is Not Known When You Write the Code
Chapter 6.2 §7 established that an array's size must be a constant expression — something the compiler can work out while compiling. That is fine when the number is a property of the problem: eight fault bits, four gain settings, sixteen channels.
It fails when the number is a property of the data.
/* How many readings will arrive? */
int readings[???];There are only two ways to answer that with a plain array, and both are bad.
Size it for the worst case. Pick a number you believe nothing will exceed, and accept that every program run reserves that much whether it needs it or not. A program handling anything from 3 to 3,000,000 readings reserves for 3,000,000 always.
Size it for the typical case and hope. This is worse, because exceeding it is Chapter 6.4's undefined behaviour rather than a clean failure.
Chapter 6.2 §7 also covered the apparent escape — a non-constant size creates a variable-length array — and explained why this curriculum does not use them: they were made optional in C11, several coding standards forbid them outright, and their storage comes out of the same finite region §4 is about. They move the problem rather than solving it.
A plain array cannot have a size chosen while the program runs. When the count genuinely depends on the input, nothing in Modules 1 to 8 can express it.
3. The Second Wall: the Lifetime Fits No Block
Chapter 9.2 was unambiguous: returning a pointer to an automatic object is always wrong, because the lifetime ends at the closing brace. Chapter 9.3 offered static storage duration and immediately disqualified it as a general fix, because one object per program means callers silently overwrite each other.
So consider a function that must produce something the caller keeps:
/* Build a result whose size depends on the input,
hand it back, and let the caller decide when it is finished with. */Work through the options already available:
| Option | Why it fails here |
|---|---|
| Return by value | the result is not a single scalar |
| Automatic object, return a pointer | Chapter 9.2 — the lifetime ends at return |
| Static object | Chapter 9.3 — one per program; a second call destroys the first |
| Caller supplies the buffer | requires the caller to know the size in advance — Wall 1 again |
The last row is the interesting one, because it is the option this curriculum has leaned on since Chapter 7.5, and it only works when the caller can size the buffer. When the size depends on work the function has not done yet, the caller cannot.
There is a gap between "as long as this block" and "as long as the program". Neither automatic nor static storage duration can express a lifetime that the programmer chooses.
4. The Third Wall: There Is Too Much of It
void process(void)
{
int samples[4000000]; /* 16 MB of automatic storage */
...
}That is a legal declaration with a constant size, so Wall 1 does not apply. It will still very likely break the program.
Chapter 9.4's model explains why: automatic objects live in a region that is finite, and whose size is an implementation and platform decision rather than anything C specifies. Chapter 5.8 already asked you to judge recursion depth against it. A single large local can exhaust it just as effectively as deep recursion.
What happens when it is exhausted is not something the standard describes an outcome for. On a hosted system the program is usually terminated; on an embedded target without that protection, Chapter 5.7 §5 noted the consequences can be worse. This chapter does not run that program, for the reason Chapter 6.4 established.
The heap is not magically larger, but it is a different region with different limits, and — crucially — a failed allocation reports failure rather than being undefined. That difference is the subject of Chapter 9.6 and is a large part of why allocation is the right tool for big objects.
Large objects belong somewhere that can refuse the request. Automatic storage cannot.
5. What Allocation Actually Gives You
Chapter 9.1's table has one row this module has not used:
| Storage duration | Lifetime begins | Lifetime ends |
|---|---|---|
| automatic | entering a block | leaving it |
| static | before main | when the program ends |
| allocated | when you ask | when you say so |
Both ends of the allocated row are under your control, and that is the entire feature. It solves all three walls at once:
Wall 1 — the amount requested is an ordinary run-time value, so it can come from the data.
Wall 2 — the lifetime is not tied to any block, so an object can outlive the function that created it and still not last forever.
Wall 3 — the request can be refused, so running out produces a value you can check rather than undefined behaviour.
And it introduces the cost that Chapters 9.8 through 9.11 are about: nothing ends the lifetime unless you do. Automatic objects are released by leaving a block, which happens whether or not you remember. Allocated objects are released only by an explicit act, and the three ways that goes wrong — never doing it, doing it twice, and using the object afterwards — are four chapters of this module.
6. The Test
Three questions, answered before you write malloc:
1. Is the number of elements known when I write the code?
yes -> a plain array
no -> wall 1
2. Does the object's life match a block, or the whole program?
a block -> an automatic object
whole program -> a static object, if it is genuinely one per program
neither -> wall 2
3. Is the total small enough to sit comfortably in automatic storage?
yes -> an automatic object
no -> wall 3If every answer points at an array or an automatic object, use one. If any answer lands on a wall, Chapter 9.6 is where you go — and you take on the obligations that come with it deliberately, knowing what they are.
Worked judgements
| Requirement | Wall? | Use |
|---|---|---|
| Sixteen fault counters | none | int counters[16] = {0}; |
| A 64-character label built in one function and printed there | none | a local array |
| A label built by a helper and used by the caller | none | caller supplies the buffer — Chapter 7.7 |
| Readings whose count arrives with the data | 1 | allocation |
| A result whose size the caller cannot know in advance | 1 and 2 | allocation |
| A 16 MB table | 3 | allocation |
| A lazily built lookup table, one per program | none | a static object — Chapter 9.3 |
Note how many rows say none. That ratio is roughly right for real code, and it is the point of the chapter.
7. What You Should Be Able to Say Now
- Automatic objects, arrays, return values, caller-supplied buffers and static objects cover most needs.
- Wall 1 — an array's size must be a constant expression, so a count that comes from the data cannot be expressed.
- Sizing for the worst case wastes; sizing for the typical case is undefined behaviour when exceeded.
- Variable-length arrays move the problem rather than solving it — Chapter 6.2 §7.
- Wall 2 — there is a gap between "as long as this block" and "as long as the program", and neither duration fills it.
- A caller-supplied buffer only works when the caller can size it.
- Wall 3 — automatic storage is finite, and exceeding it is not something the standard gives an outcome for.
- Allocated storage duration begins when you ask and ends when you say so — both ends under your control.
- A failed allocation reports failure; exhausting automatic storage does not.
- Allocation is not the default. Ask the three questions; most of the time the answer is a plain array.
8. Exercises
Exercise 1 — apply the test. For each requirement, run §6's three questions and say what you would use.
- a 32-bit status word's eight fault counters
- a buffer for a message of at most 120 characters, used only inside one function
- the readings from a sensor, however many arrive this run
- a table of 12 month lengths
- a 64 MB working buffer for an image
Exercise 2 — name the wall. For each, say which wall is in the way, or that none is.
int a[n]; /* n read from the input */
char *make_message(void) { char buf[64]; ...; return buf; }
void f(void) { double big[2000000]; ... }
int counts[8] = {0};Exercise 3 — explain the gap. Say in two sentences why neither automatic nor static storage duration can express "lives until the caller is finished with it", referring to Chapter 9.2 and Chapter 9.3.
Exercise 4 — judge the caller-supplied buffer. The pattern from Chapter 7.7 solves many problems and not all of them. Give one requirement it handles well and one it cannot, and say which wall the second one hits.
Exercise 5 — compare the failures. A program asks for more automatic storage than is available, and another asks for more heap than is available. Describe what each one can do about it, and say which of the two is undefined behaviour.
Exercise 6 — argue against allocating. A colleague proposes allocating every buffer in a small embedded program "for flexibility". Give three properties they would be giving up, and name the chapters that cover the bugs they would be taking on.
Exercise 7 — count the rows. In §6's table, most rows say no wall is in the way. Say what that implies about how often allocation is the right answer, and what you would check first when reviewing code that allocates.
Exercise 8 — predict the obligations. Allocated lifetime ends only when you say so. Without reading ahead, name the three things that can go wrong as a direct consequence, and say which is likely to be hardest to find.
9. Summary
- Most needs are already met by arrays, automatic objects, return values, caller-supplied buffers and statics.
- Wall 1 — size. An array's size is a constant expression; a count from the data cannot be one.
- Wall 2 — lifetime. Neither "this block" nor "the whole program" expresses "until the caller is done".
- Wall 3 — amount. Automatic storage is finite, and exceeding it has no defined outcome.
- Allocated storage duration begins and ends when you choose, which solves all three.
- A failed allocation can be checked; exhausting automatic storage cannot.
- The cost: nothing releases it unless you do — the subject of Chapters 9.8 to 9.11.
- Allocation is not the sophisticated default. Run the three questions first; most answers are a plain array.
The three walls are real, the third storage duration removes all of them, and it does so with two function calls that between them constitute the smallest interface in this module and the largest source of bugs in the language. The next chapter introduces both, along with the two habits that have to become automatic — checking every request before using what it returned, and pairing every successful request with exactly one release.
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.
