C++ Part I · Module 10 · Dynamic Memory and Object Lifetime
Object Lifetime: When an Object Begins and Ends
Four chapters have now described this subject in the language of memory — frames reserved and released, blocks obtained and handed back, element counts tucked in beside the elements. That vocabulary was the right way to build the mechanism and it is not the vocabulary the language itself uses, and the gap begins to matter here. The standard talks about an object's lifetime: the interval that begins when its construction finishes and ends when its destruction starts. Storage is a separate question, and a smaller one. Making that the primary idea is not a philosophical preference; it is what turns the remaining failure modes from vague badness into precise statements. A leak is a lifetime that never ended. A use-after-free is an access to an object whose lifetime already did. Both sentences are exact, and neither mentions the heap. This chapter establishes the idea with a single traced program in which objects of all three storage durations announce their own beginnings and endings, so the whole sequence can be read off in order — including the behaviour of one object created before the program's entry point and destroyed after it returns. The most important thing in that trace is a line that is missing, and it is worth finding before reading the explanation.
Intermediate17 min readC++Part IFundamentalsMemory
Part I · Module 10 · Chapter 10.5 · Object Lifetime: When an Object Begins and Ends
1. A Change of Vocabulary
Chapters 10.2 to 10.4 used a picture: the stack grows down, a frame is released, a block is handed back. That picture was necessary and it is about to start getting in the way.
The language's own idea is simpler and more precise:
An object's lifetime begins when its construction completes and ends when its destruction begins.
Storage — where the bytes are, which region they came from, how they are tracked — is a separate and lesser question. Chapter 7.2 separated lifetime from scope; this chapter separates lifetime from storage, which is the cut that makes the rest of the module sayable.
2. What the Definition Buys
Three statements that are awkward in the memory picture and exact in this one:
A leak is a lifetime that never ended. Not "memory that was not returned" — the destructor never ran, and if it was supposed to close a file or release a lock, that did not happen either. Chapter 10.6 is about this.
A use-after-free is an access to an object whose lifetime has ended. Whether the bytes still hold the old value is irrelevant to whether the program is correct. Chapter 10.7 measures bytes that do still hold something, and the program is wrong regardless.
An object and its storage are different things. A frame's bytes exist before and after the object in them; Chapter 10.3 §3 measured the four steps precisely because storage and object are acquired separately.
3. The Trace
One program, all three storage durations, each object announcing itself.
#include <cstdio>
struct Tracer {
const char* name;
explicit Tracer(const char* n) : name(n) { std::printf(" + %s begins\n", name); }
~Tracer() { std::printf(" - %s ends\n", name); }
};
static Tracer global("global (static storage)");
static void automaticScope()
{
std::printf("automatic, two objects in one scope:\n");
Tracer a("a");
Tracer b("b");
{
Tracer inner("inner block");
}
std::printf(" (leaving the function)\n");
}
int main()
{
std::printf("main entered\n\n");
automaticScope();
std::printf("\ndynamic — lifetime decoupled from any scope:\n");
Tracer* d = new Tracer("dynamic d");
std::printf(" (leaving the block that created it)\n");
{
Tracer* e = new Tracer("dynamic e, never deleted");
(void)e;
}
std::printf(" (that block has ended — did e end?)\n");
delete d;
std::printf("\nmain returning\n");
return 0;
}Measured output, complete:
+ global (static storage) begins
main entered
automatic, two objects in one scope:
+ a begins
+ b begins
+ inner block begins
- inner block ends
(leaving the function)
- b ends
- a ends
dynamic — lifetime decoupled from any scope:
+ dynamic d begins
(leaving the block that created it)
+ dynamic e, never deleted begins
(that block has ended — did e end?)
- dynamic d ends
main returning
- global (static storage) endsRead it before continuing, and in particular count the + lines and the - lines.
4. The Missing Line
There are five + lines and four - lines.
- dynamic e, never deleted ends does not appear. Not late, not out of order — absent. The object was constructed and never destroyed, and the trace shows exactly that: a beginning with no end.
This is the most useful picture of a leak in the module, and it is better than a byte count because it says what was actually lost. If Tracer's destructor had closed a log file, flushed a buffer, released a simulator handle or decremented a reference count, none of that happened. The bytes are a side issue; the missed work is the problem.
Note also what did not rescue it. The enclosing block ended — (that block has ended — did e end?) prints immediately after — and nothing happened. main returned, and nothing happened. The program exited, and nothing happened. Chapter 10.3 §9 stated there is no sweep at exit; this is that statement observed.
The pointer e had automatic storage duration and its lifetime ended at the block's brace, correctly and automatically. The object it pointed at was unaffected by that, because the lifetime of a pointer and the lifetime of its target are unrelated. That single sentence is the source of nearly every bug in the next three chapters.
5. The Rest of the Trace
Static: constructed before main, destroyed after it. + global precedes main entered, and - global follows main returning. Chapter 7.4 established static storage duration; this is it observed from both ends. (The order in which several such objects across different files are constructed is a real and thorny problem — the static initialization order problem — which belongs to Module 24.)
Automatic: strict nesting, reverse destruction. a then b begin in order; the inner block's object begins and ends at its own closing brace, before the function continues; then at function exit b ends and then a. Reverse order, exactly as Chapter 10.4 §3 measured for array elements and for the same dependency reason.
Dynamic: no relationship to scope at all. d was created inside main, survived the block boundary that followed, and ended precisely at delete d — not a moment earlier or later. That is the capability Chapter 10.1 §3 identified as unobtainable any other way, and the trace shows it working.
So the three durations differ in who decides the end:
| Duration | Lifetime ends | Decided by |
|---|---|---|
| static | after main returns | the runtime |
| automatic | at the closing brace | the compiler |
| dynamic | at delete | you |
Every chapter remaining in this module is about the consequences of that third row.
6. Lifetime and Storage Are Different
The distinction the chapter's purpose insists on, stated plainly with the measurements already in hand.
Storage can exist without an object. Chapter 10.3 §3's step [1] obtained 4 bytes before any Probe existed; step [2] then created one there. Between them there was storage and no object.
Storage can outlive the object in it. Chapter 10.4 §5 skipped two destructors — their storage was handed back while, formally, their lifetimes had never ended.
Storage can be reused by a different object. Chapter 10.2 §4 made this the basis of its bug: the released frame is the next frame handed out.
And the bytes surviving says nothing about the object. Chapter 10.7 reads a freed block and finds a value in it. The object is gone; some bytes remain. (The facility for deliberately creating a new object in storage you already hold is placement new, named in Chapter 10.3 §9 and left to Part II.)
The practical form of this: "is the memory still there?" is the wrong question. The right one is "has this object's lifetime ended?" — and for a dynamic object the answer is decided entirely by whether something called delete, not by what the bytes currently contain.
7. Why the Memory Picture Misleads
Chapter 10.2's diagram and Chapter 10.3's four steps were worth building, and from here they are a liability if they stay primary. Three concrete ways they mislead:
"Freed memory is gone." It is not — nothing is erased, and Chapter 10.7 reads it. A reader who pictures freeing as erasing will be surprised that a use-after-free works, and will mis-diagnose it when it eventually does not.
"The stack is fast, the heap is slow." A caricature. What is actually true is narrower and was measured: Chapter 10.2 §2 showed frame reservation is a register adjustment, Chapter 10.3 §7 showed a 16-byte slot for a 4-byte object, and Chapter 9.3 measured a 60× penalty for a traversal the hardware disliked — which was about layout, not about which region the data was in.
"A leak means memory is lost." The narrowest reading of §4. A leak is a destructor that never ran, and memory is merely the most visible of the things it did not do.
So: keep the mechanism for reasoning about cost, and use lifetime for reasoning about correctness. They answer different questions and the module's bugs are all correctness.
8. The Two Failure Modes, Restated
With the vocabulary in place, the next two chapters can be stated in one line each.
A lifetime that never ends — a leak. The object persists, its destructor never runs, and its storage is never reclaimed. Chapter 10.6 measures this both as a growing number and as the tool report that finds it.
An access after a lifetime has ended — use-after-free. The object is gone; the pointer is not. Chapter 10.7 measures that the program often continues, with a wrong value rather than a crash.
And a lifetime ended twice — double delete. Destruction begun on an object whose lifetime already ended. Also Chapter 10.7, and it behaves quite differently from the other.
Three failures, one cause: for a dynamic object, the end of the lifetime is a decision your code makes, and nothing checks that the decision was made exactly once. Chapter 10.8 shows that making it reliably is harder than it looks even when you are trying, and Chapter 10.9 names what is actually missing.
9. What to Actually Do
Ask "when does this object's lifetime end?" for every object you create — and for a dynamic one, be able to point at the code that ends it.
Reason about correctness in lifetimes and about cost in memory. Mixing them is what produces "it worked, so it's fine".
Treat a missing destructor as the loss, not the bytes. §4's absent line is what a leak actually is.
Remember a pointer's lifetime and its target's are unrelated. The pointer ending changes nothing about the object.
Expect no cleanup at exit. The trace shows main returning with an object still alive.
Rely on reverse destruction. It is guaranteed for automatics and array elements alike.
Prefer automatic objects, because their end is decided by the compiler and therefore cannot be forgotten.
When a dynamic lifetime has no obvious end, stop. That is Chapter 10.9's question arriving early, and it is the right moment to ask it.
10. Mistakes Worth Making Once
Thinking a leak is about bytes. It is a destructor that never ran, and the file stayed open.
Expecting the enclosing block to clean up a dynamic object. The trace shows it does not.
Expecting program exit to clean up. It does not.
Confusing the pointer's lifetime with the object's. e ended properly; its target never did.
Believing freed memory is erased. Nothing is erased — Chapter 10.7 reads it.
Asking "is the memory valid?" Unanswerable; ask whether the lifetime ended.
Assuming objects are destroyed in creation order. Reverse, always.
Treating the stack/heap picture as the model of correctness. It is a model of cost.
11. Interview Relevance
"When does an object's lifetime begin and end?" Begins when construction completes, ends when destruction begins — and storage is a separate question. Crisp and often fumbled.
"What is a memory leak?" The answer that stands out is "a lifetime that never ended, so the destructor never ran" — and then noting that the memory is only the most visible consequence, since the same bug leaves files open and locks held.
"In what order are objects in a scope destroyed?" Reverse of construction. Follow-up: the same rule applies to array elements, for the same dependency reason.
"When are objects with static storage duration constructed and destroyed?" Before main and after it — measurable, as in §3 — with the initialization-order problem as the honest caveat.
"Why is RAII the answer?" Because automatic lifetimes are ended by the compiler on every path, so putting the release in an automatic object's destructor borrows a guarantee that already exists. Reaching that from the lifetime framing is a much stronger answer than reciting the acronym.
12. What You Should Be Able to Say Now
- A lifetime begins when construction completes and ends when destruction begins — storage is separate and lesser.
- A leak is a lifetime that never ended; a use-after-free is an access after one did; a double delete ends one twice.
- Measured: the static object was constructed before
main enteredand destroyed aftermain returning. - Measured:
a,bconstructed in order and destroyedbthena— reverse. - Measured: the inner block's object ended at its own brace.
- Measured: the dynamic object survived its enclosing block and ended exactly at
delete. - Measured: the fifth
-line never appeared — five beginnings, four endings. - Neither the enclosing block, nor
mainreturning, nor program exit ended it. - A pointer's lifetime and its target's are unrelated — the source of most of this module's bugs.
- The three durations differ in who decides the end: runtime, compiler, you.
- Storage can exist without an object, outlive one, and be reused by another.
- "Is the memory valid?" is unanswerable; "has the lifetime ended?" is answerable by reading the code.
- The lifetime framing covers files, locks and handles, not just memory.
- RAII follows directly: put the dynamic release in an automatic object's destructor and borrow the compiler's existing guarantee.
13. Exercises
Exercise 1 — count them. Without rerunning it, say how many + and - lines the trace produces and which specific line is absent.
Exercise 2 — order it. Write the exact output for a function declaring Tracer x("x");, then a block containing Tracer y("y");, then Tracer z("z");.
Exercise 3 — restate. Express each in lifetime terms without mentioning memory: (a) a leak (b) a use-after-free (c) a double delete (d) returning a pointer to a local.
Exercise 4 — beyond memory. Tracer's destructor closes a file. Say precisely what goes wrong in the dynamic e case and why no leak checker would report the real damage.
Exercise 5 — two lifetimes. For { Tracer* e = new Tracer("e"); } describe both lifetimes involved, when each ends, and why one ending does not affect the other.
Exercise 6 — who decides. Complete the §5 table from memory and explain why only the third row can go wrong.
Exercise 7 — derive the fix. Using only §5's table, argue your way to RAII without using the word, then state what the destructor must contain.
14. Summary
- A lifetime begins when construction completes and ends when destruction begins; storage is a separate, lesser question.
- Measured, one trace, all three durations: static spans
mainon both sides; automatics nest strictly and destroy in reverse; a dynamic object ends only atdelete. - Measured: one expected destructor line never appeared — the clearest picture of a leak in the module.
- Nothing rescued it — not the block, not
mainreturning, not exit. - A pointer's lifetime is unrelated to its target's.
- The durations differ in who ends the lifetime: runtime, compiler, you — and only the last is unchecked.
- Storage can exist without an object, outlive one, or be reused by another.
- Ask whether the lifetime ended, not whether the memory looks valid.
- The framing extends to files, locks and handles, where no sanitizer helps.
- It makes RAII obvious: let an automatic object's destructor end the dynamic lifetime.
The vocabulary is now precise enough to go after the failures themselves, and the first one is the kind that does not announce itself at all. A lifetime that never ends produces no crash, no warning and no wrong answer — the program behaves correctly in every observable way and simply uses a little more memory than it should, which in a program that runs for a second is indistinguishable from nothing. The next chapter is about why that is a serious bug rather than an untidy one, which takes a program that runs for longer than a second, and about finding such defects by a method rather than by reading code hopefully. Both halves are measured: the same loop two hundred thousand iterations deep, once leaking and once not, and a tool that reports the exact source line responsible.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 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.
