C++ Part I · Module 7 · Scope, Storage, and Namespaces
Storage Duration: Automatic, Static, Thread and Dynamic
Chapter 7.2 answered the question when does this object exist three separate times — once for an ordinary local, once for a static local, once for an object created before main — and presented those as three facts to know. They are not three facts. The standard sorts every object into one of exactly four categories called storage durations, and the answer to when does it exist follows from which category an object is in, rather than from where in the source it happened to be written. That makes this chapter a classification task, which is unusually tractable: there are four categories, the rule that assigns an object to one is short, and most of the time you are not choosing at all — the way you declared the object chose for you. What makes the categories feel real rather than bureaucratic is that they are visible. A single program that prints one address per category shows them separating: the two objects with static duration landed four bytes apart, the thread-local one sat in an entirely different part of the address space, and three automatic objects created by three nested calls marched downward in even thirty-two-byte steps, one per call.
Intermediate16 min readC++Part IFundamentalsScope
Part I · Module 7 · Chapter 7.3 · Storage Duration: Automatic, Static, Thread and Dynamic
1. One Property, Four Values
An object's storage duration is the category that determines when its storage is obtained and when it is released — and therefore how long the object can live.
Every object has exactly one. There is no object without a storage duration and none with two.
The four:
| Duration | Storage obtained | Released | Declared as |
|---|---|---|---|
| automatic | on entry to its block | on exit from that block | an ordinary local or parameter |
| static | before first use, at program start-up | at program exit | static, or at namespace scope |
| thread | when a thread starts | when that thread ends | thread_local |
| dynamic | when you ask | when you release it | explicitly — Module 10 |
Chapter 7.2's three answers were the first two rows. This chapter adds the other two and, more usefully, replaces "three facts" with "one property with four values".
You usually do not choose. Write int n = 0; inside a function and it has automatic duration; write the same line outside every function and it has static duration. The declaration's position chose. Only thread_local and the dynamic form are things you ask for in so many words.
2. The Rule That Assigns One
Four questions, in order. The first "yes" is the answer.
- Did you request it explicitly (Module 10's mechanism)? → dynamic
- Is it declared
thread_local? → thread - Is it declared
static, or at namespace scope? → static - Otherwise → automatic
That is the whole classification. Chapter 7.2's static int calls = 0; inside a function hits question 3; its int local = 11; falls through to question 4.
3. The Four, Measured
One program, one object per category, each printing where it lives:
#include <iostream>
static int global_object = 1; // static storage duration
thread_local int per_thread_object = 2; // thread storage duration
static void show_automatic(int depth)
{
int automatic_object = depth; // automatic storage duration
std::cout << " automatic (depth " << depth << ") at "
<< static_cast<const void*>(&automatic_object) << '\n';
if (depth < 3) show_automatic(depth + 1);
}
int main()
{
static int function_local_static = 3; // static storage duration
std::cout << "static (namespace scope) at "
<< static_cast<const void*>(&global_object) << '\n';
std::cout << "static (function-local) at "
<< static_cast<const void*>(&function_local_static) << '\n';
std::cout << "thread_local at "
<< static_cast<const void*>(&per_thread_object) << '\n';
show_automatic(1);
return 0;
}Measured output:
static (namespace scope) at 0x104908004
static (function-local) at 0x104908000
thread_local at 0x10509daf0
automatic (depth 1) at 0x16b4fe878
automatic (depth 2) at 0x16b4fe858
automatic (depth 3) at 0x16b4fe838Three relationships, and they are the point rather than the numbers.
The two static objects are four bytes apart. …004 and …000 — adjacent, in one region, despite one being declared at namespace scope and the other inside main. Different scopes, same duration, same region. That is Chapter 7.1 and 7.2's separation made visible in one line of output.
thread_local is somewhere else entirely. 0x10509daf0 against 0x104908000 — a different region, because per-thread storage has to be duplicated per thread and cannot sit in the single shared block the statics occupy.
The three automatic objects descend in even steps. 32 bytes between each, from …878 down to …838. Three nested calls, three live objects at once, each getting its own storage below the last. This is the structure Chapter 10.2 is about.
Run it again and every base address moves, exactly as Chapter 6.2 §3 warned: a second run gave 0x102204004 / 0x102204000 / 0x1029ddaf0 and automatics at 0x16dc02878 / …858 / …838. Every relationship held — statics four bytes apart, thread-local elsewhere, automatics 32 bytes apart descending. The relationships are the finding; the hex is not.
4. Automatic
The default, and the one almost everything you write has.
Storage is obtained when control enters the block and released when it leaves — by any route, including a return from the middle, a break, or (later) a thrown exception. Chapter 6.2 established that a parameter is created at the call and destroyed at the return; that is this duration.
Two consequences already measured:
The storage is reused. Chapter 7.2 §4 showed two locals in two functions at the same address, because the first's storage was released before the second's was obtained.
It nests. §3's three addresses show three live automatic objects at once, one per active call.
The useful mental rule: an automatic object exists exactly as long as the block it was declared in is executing. Nothing about it survives the closing brace, which is why Chapter 7.2 §6's returned reference is a defect.
5. Static
Storage lasts from program start-up to program exit. Two very different declarations produce it:
int at_namespace_scope = 1; // static duration
void f() { static int inside_a_function = 2; } // also static duration§3 measured both landing in the same region four bytes apart.
The two have identical duration and completely different scope. The first's name is visible across the whole translation unit; the second's is a compile error outside f — measured in Chapter 7.2 §3. One duration, two scopes, which is the module's whole point.
A warning about the word static. It means two different things in C++ and this chapter only uses one of them:
| Where written | What it means |
|---|---|
| on a local, inside a function | static storage duration — this chapter |
| at namespace scope, on a function or object | internal linkage — Chapter 7.5 |
That is a genuine wart. static int x; inside a function changes the object's duration; static int x; outside every function changes its linkage and leaves its duration exactly as it already was. Every example in Module 6 used the second meaning, which is why Chapter 6.1 §3 could only ask you to take it on trust. Chapter 7.5 pays that debt.
When to reach for a static local deliberately: a counter across calls, a cache computed once. Chapter 6.6 §4 used one to count default-argument evaluations. Chapter 7.4 is required reading before you rely on one, because when its initializer runs is the part with rules.
6. Thread
Storage lasts for the duration of one thread, and each thread gets its own copy.
thread_local int per_thread_object = 2;§3 measured it in a region separate from the statics, which is the mechanism showing: one object per thread cannot live in a single shared location.
This curriculum's Module 26 is where threads are the subject, and nothing in Part I creates one. The reason to name the duration now is that the classification in §2 is only complete with it, and because you will meet thread_local in real code long before you write a thread yourself — typically on a per-thread error state or log buffer.
What you can take from it today: it is a fourth duration, declared with one keyword, and it is the right answer when an object must be per-thread rather than shared. Everything about why you would want that is Module 26.
7. Dynamic
The fourth duration is the one you ask for explicitly, and the only one where releasing the storage is your responsibility.
Module 10 is the whole subject — nine chapters, from why it exists through ownership. Borrowing one line of it shows the region:
#include <iostream>
static int global_object = 1;
int main()
{
int automatic_object = 2;
int* dynamic_object = new int(3); // Chapter 10.3's subject, borrowed
std::cout << "static at " << static_cast<const void*>(&global_object) << '\n';
std::cout << "automatic at " << static_cast<const void*>(&automatic_object) << '\n';
std::cout << "dynamic at " << static_cast<const void*>(dynamic_object) << '\n';
delete dynamic_object;
return 0;
}Measured output:
static at 0x104734000
automatic at 0x16b6d28a8
dynamic at 0x104761a50Read that honestly. It would be tidier to say the three durations live in three cleanly separated regions, and the measurement does not support it. The dynamic object landed at 0x104761a50, which is near the static region at 0x104734000 and very far from the automatic object at 0x16b6d28a8. So what this measurement shows is that automatic storage is somewhere quite different, and that dynamic and static storage happened to be allocated nearby here.
That is a fact about this implementation and this run, not about C++ — Chapter 1.3 §5's rule again. The standard says nothing about where any of these live; it says when each is obtained and released, which is what the durations are about.
int* is a pointer and Chapter 8.1 is its chapter; here it is the thing new hands back and the thing delete takes. Nothing else from Module 10 is borrowed, and nothing in Part I before Module 10 will use new again.
8. Which One Does This Object Have?
The frozen purpose asks you to be able to say, for any object you create, which duration it has. §2's four questions are the procedure; here it is applied to every form that has appeared in this curriculum so far.
| Declaration | Duration | Why |
|---|---|---|
int n = 0; in a function | automatic | question 4 |
| a parameter | automatic | question 4 |
a for loop's int i | automatic | question 4 |
int n = 0; at namespace scope | static | question 3 — namespace scope |
static int n = 0; in a function | static | question 3 — static |
static int n = 0; at namespace scope | static | question 3 — and it also changes linkage, Chapter 7.5 |
| a string literal's characters | static | they exist for the whole program |
thread_local int n; | thread | question 2 |
new int(3) | dynamic | question 1 |
| a temporary from Chapter 6.5 §5 | automatic | it dies at the end of the full expression |
Two rows are worth a second look.
The string literal. Every "hello" in this curriculum has been an object with static storage duration — which is why Chapter 6.3's return "slow"; from a function returning const char* is safe, where returning a reference to a local is not. The characters outlive the call because they were never in the call's storage.
The temporary. Chapter 6.5 §5 measured a const int& parameter binding to the literal 42, and the address printed was neither of the caller's objects. That temporary has automatic duration with a lifetime bounded by the full expression — safe to read during the call, which is exactly what that chapter concluded.
9. What to Actually Do
Default to automatic. It is the right answer for nearly everything, and its lifetime is visible in the source.
Reach for a static local only for a genuine cross-call need — a counter, a cache — and read Chapter 7.4 first.
Know which static you are writing. Inside a function it is a duration; at namespace scope it is a linkage — Chapter 7.5.
Do not use dynamic storage yet. Module 10 is nine chapters because getting it right is the subject; nothing in Part I up to Module 9 needs it.
Treat region observations as measurements, not guarantees. The durations are portable; the addresses are not.
When asked how long an object lives, name its duration rather than describing its scope. The duration is the answer; the scope is a different question.
10. Mistakes Worth Making Once
Thinking an object's lifetime follows from where its name is visible. Measured in Chapter 7.2 both ways; the duration is what decides.
Confusing the two meanings of static. Inside a function it sets duration; at namespace scope it sets linkage.
Assuming an object at namespace scope needs static to have static duration. It already has it — static there does something else.
Expecting three durations in three tidy regions. Measured: dynamic landed near static and far from automatic.
Treating "stack" and "heap" as standard terms. The standard defines timing, not placement.
Relying on an address relationship. Every base address moved between runs; only the relationships held.
Forgetting that string literals have static duration. It is why returning one is safe and returning a reference to a local is not.
11. Interview Relevance
"What storage durations does C++ have?" Four: automatic, static, thread, dynamic. Naming all four — including thread, which is the one people forget — and saying that every object has exactly one is the complete answer.
"What is the difference between a static local and a global?" The trap is that they differ in scope, not in duration. Both have static storage duration; one's name is visible in a function, the other's across a translation unit. Chapter 7.2 §3's measurement is the evidence.
"Where do local variables live?" The strong answer distinguishes the question from the answer: they have automatic storage duration, which says when their storage is obtained and released; where is an implementation matter, usually a stack, and the standard does not say. Candidates who answer only "the stack" have the usual implementation and not the language.
12. What You Should Be Able to Say Now
- Storage duration decides when an object's storage is obtained and released, and every object has exactly one.
- The four: automatic, static, thread, dynamic.
- The assignment rule, in order: requested explicitly → dynamic;
thread_local→ thread;staticor namespace scope → static; otherwise → automatic. - You usually do not choose — the declaration's position chooses for you.
- Measured: the two static objects landed four bytes apart despite having different scopes.
- Measured:
thread_localsat in a different region, because per-thread storage must be duplicated. - Measured: three nested automatic objects descended in 32-byte steps, one per active call.
- Every base address moved between runs; every relationship held.
- Automatic storage is released on every exit from the block, which is why its storage gets reused — Chapter 7.2 §4.
- Static covers both a namespace-scope object and a
staticlocal: same duration, different scope. staticmeans two things — a duration inside a function, a linkage at namespace scope (Chapter 7.5), which is a genuine wart.- Measured honestly: dynamic storage landed near static and far from automatic — not three tidy regions.
- The standard defines timing, not placement; "stack" and "heap" are implementation vocabulary.
- String literals have static duration, which is why returning one is safe; a Chapter 6.5 temporary has automatic duration bounded by the full expression.
13. Exercises
Exercise 1 — classify each. Give the storage duration and the question from §2 that decided it: (a) a parameter (b) static int hits = 0; in a function (c) int limit = 8; at namespace scope (d) thread_local int id; (e) a for loop's index (f) the characters of "idle".
Exercise 2 — read the addresses. From §3's output, state the three relationships and, for each, say whether it is a language guarantee or an observation.
Exercise 3 — predict the steps. §3's program recurses to depth 3. Say how many automatic objects named automatic_object exist simultaneously at the deepest point, and what you expect of their addresses.
Exercise 4 — same duration, different scope. Write the two declarations that produce static duration with the two different scopes, and say which one Chapter 7.2 §3 measured as a compile error from outside.
Exercise 5 — the two meanings. For each static, say whether it sets a duration or a linkage: (a) static int n = 0; inside main (b) static int helper(int); at namespace scope (c) static int total = 0; at namespace scope (d) static int calls = 0; inside a function.
Exercise 6 — the honest reading. §7 measured dynamic storage near the static region. Say what that does and does not establish, and name the chapter whose rule governs how you should report it.
Exercise 7 — why it is safe. const char* name() { return "idle"; } is safe, while int& value() { int v = 1; return v; } is not. Explain the difference using storage durations only.
14. Summary
- Storage duration decides when storage is obtained and released; every object has exactly one of four.
- Automatic, static, thread, dynamic — and the assignment rule is four questions, first yes wins.
- The declaration's position usually chooses for you.
- Measured: two statics 4 bytes apart,
thread_localin a different region, three automatics 32 bytes apart descending. - Base addresses moved between runs; relationships held.
- Static duration covers a namespace-scope object and a
staticlocal — same duration, different scope. staticmeans a duration in a function and a linkage at namespace scope — Chapter 7.5.- Measured honestly: dynamic landed near static, far from automatic — not three tidy regions.
- The standard defines timing, not placement.
- String literals are static; a
const&temporary is automatic, bounded by the full expression.
Two of these four durations have something the others do not: an object that lives for the whole program has to be initialized at some point before anybody uses it, and the question of exactly when has real consequences. For a static local the answer turns out to be precise and useful — it is initialized the first time control passes through its declaration, which means a function never called never pays for it. For an object at namespace scope the answer is precise about one thing and deliberately silent about another, and the silence is the famous part. The next chapter measures both, including one program that was compiled once, linked twice in different orders, and initialized its two objects in a different sequence each time.
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.
