Skip to content
VLSI Mentor

C++ Part I · Module 7 · Scope, Storage, and Namespaces

Lifetime and Scope Are Different Questions

Chapter 7.1 was about names and said so repeatedly, deferring every question about objects to this chapter. Here is why that separation was worth insisting on. Scope is decided entirely at compile time and tells you where a name may be written; lifetime is a run-time property of an object and tells you when it exists. Those are not two descriptions of one thing, and the way to see it is to find cases where they disagree. Both exist and both are ordinary. A variable declared static inside a function keeps its value from one call to the next, which means the object is alive for the whole program — while its name is a compile error anywhere outside that function, so the object outlives any ability to refer to it by name. And in the other direction, two locals in two different functions, with different names and different values, can be measured sitting at exactly the same address, because the first stopped existing before the second began. Once both of those are on the table, the warning Chapter 6.3 met without explanation stops being a special rule to memorise. A reference to a destroyed object is simply the second case: the name is still perfectly in scope, and the object it names is gone.

Intermediate17 min readC++Part IFundamentalsScope

Part I · Module 7 · Chapter 7.2 · Lifetime and Scope Are Different Questions

1. Two Properties of Two Different Things

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycles = 7;

That one line creates an object and introduces a name for it, which is why the two get conflated. But the properties belong to different things:

Belongs toDecidedAnswers
Scopethe nameat compile timewhere may I write this?
Lifetimethe objectat run timewhen does this exist?

Chapter 3.1 established the distinction this rests on: a variable is a name for an object, and an object is typed storage with a lifetime. Chapter 7.1 took the name half. This chapter takes the object half, and then shows that the two halves can come apart.

The lifetime of an object is the interval between its initialization and its destruction. Nothing about that interval is determined by where its name can be written.

2. The Two Axes Are Independent

The claim this chapter has to earn is that these are two axes rather than one. The way to earn it is to find programs in each of the two off-diagonal cases:

  • An object that is alive where its name is not visible — §3.
  • A name that is visible where its object is not alive — §4.

If either were impossible, the two ideas would collapse into one and Chapter 7.1 would have been enough. Both are possible, both are ordinary, and both are measured below.

A note on the measurements. This chapter argues almost entirely from addresses, so Chapter 6.2 §3's caveat applies throughout and is worth restating: the exact hex values differ on your machine and between two runs on the same machine. The claim is never a particular number — it is the relationship between numbers printed by one run.

3. An Object That Outlives Its Name

Azvya Education Pvt. Ltd.VLSI Mentor
outlives.cpp — complete program
#include <iostream>

static int counter()
{
    static int calls = 0;        // one object, created once, never destroyed until exit
    ++calls;
    std::cout << "  calls = " << calls
              << "   at address " << static_cast<const void*>(&calls) << '\n';
    return calls;
}

int main()
{
    counter();
    counter();
    counter();
    // the NAME `calls` is not visible here at all -- but the object is still alive
    return 0;
}

Measured output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  calls = 1   at address 0x1021ac000
  calls = 2   at address 0x1021ac000
  calls = 3   at address 0x1021ac000

Two facts in that output.

The value persisted. 1, then 2, then 3. The static int calls = 0; initializer did not run again on the second call — Chapter 7.4 is the chapter on exactly when it does run.

The address never changed. One object, in one place, for all three calls. It was not created and destroyed three times; it was created once.

Now the other half. Referring to that name from outside the function:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int main() { counter(); return calls; }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: use of undeclared identifier 'calls'

Put the two together. The object exists continuously from before the first call until the program ends. Its name is writable only inside counter. So for the whole of main, there is an object that is alive and has no name you are allowed to use.

That is not a trick. It is the normal behaviour of a static local, and it is the reason Chapter 6.6 §4 could use one to count how many times a default argument was evaluated. Lifetime extends well beyond scope.

4. A Name That Outlives Its Object

Now the other direction, and it is the one that matters for bugs.

Azvya Education Pvt. Ltd.VLSI Mentor
reused.cpp — complete program
#include <iostream>

static void first()
{
    int local = 11;
    std::cout << "  first():  local = " << local
              << " at " << static_cast<const void*>(&local) << '\n';
}

static void second()
{
    int other = 22;
    std::cout << "  second(): other = " << other
              << " at " << static_cast<const void*>(&other) << '\n';
}

int main()
{
    first();
    second();        // a different object, very possibly the same storage
    return 0;
}

Measured output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  first():  local = 11 at 0x16d98689c
  second(): other = 22 at 0x16d98689c

The same address. local and other are different names, declared in different functions, holding different values — and they occupied the same storage.

The reason is that local stopped existing when first returned. Its storage became available again, and when second was called the implementation used it for other. There is no conflict, because the two objects' lifetimes do not overlap.

One piece of storage can host many objects over the life of a program, one after another. An address does not identify an object; it identifies a location that an object may or may not currently occupy.

That sentence is the one to carry out of this chapter. It is also, said differently, why the next section's bug is a bug.

Is this guaranteed? No, and the distinction matters. That other reused local's exact address is what this implementation did, not what the standard requires — the same point Chapter 4.9 §5 made about evaluation order and Chapter 1.3 §5 made in general. What is guaranteed is that local's lifetime ended when first returned. The address reuse is evidence of that, not the rule itself.

5. All Four Combinations

With both off-diagonal cases established, the full picture:

Name in scopeName not in scope
Object alivean ordinary local, in use§3 — a static local, seen from main
Object not alive§6 — a reference to something destroyedan ordinary local, after its block

Three of those four are unremarkable. The top-right is §3's measurement and is perfectly safe — you simply cannot name the object. The bottom-right is every local variable you have ever written, after its closing brace.

The bottom-left is the one with teeth, because it is the only quadrant where you can write a name and get something wrong. In the other three, either there is nothing to say or the compiler stops you.

6. The Dangling Reference, Explained

Chapter 6.3 §6 measured this and could not fully explain it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    static int& f()
    {
        int local = 5;
        return local;         // local stops existing when f returns
    }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: reference to stack memory associated with local variable 'local' returned [-Wreturn-stack-address]

Now it is a corollary rather than a rule. Line by line:

local is an object with automatic storage, whose lifetime ends when f returns — §4 established that, and measured the storage being reused afterwards.

The returned reference is a name for that object. Chapter 6.4 established that a reference is another name for an object, and §1 established that a name's scope has nothing to do with the object's lifetime.

So the caller holds a perfectly valid name for an object that no longer exists. The name is in scope. The object is gone. That is the bottom-left quadrant exactly.

What happens when you use it is undefined behaviour in Chapter 1.3 §5's precise sense — the standard places no requirements on the program. And §4 is why it is so treacherous in practice: the storage is still there, it may still contain 5, and it may be handed to the next call that needs four bytes. A program that reads it may print 5, may print something else, and may change behaviour when an unrelated function is added nearby.

This is the distinction the chapter exists for. Someone who has only one idea called "scope" has to memorise "do not return a reference to a local" as an arbitrary rule. Someone who has two axes can derive it, and can derive the cases nobody told them about.

7. Where Each Kind of Object Gets Its Lifetime

A short preview, because "when does it exist" has exactly four answers and the next chapter is about them.

Kind of objectLifetime runs fromuntil
a local, or a parameterits initializationthe end of its enclosing block
a static localfirst pass through its declarationprogram exit
a namespace-scope objectbefore main beginsprogram exit
one you request explicitlywhen you ask for itwhen you release it

The first three appear in this chapter. The fourth is Module 10's subject and needs no mention beyond its existence.

What makes this a list of four rather than a list of cases is the idea of storage duration, which is the standard's name for the category an object belongs to. Chapter 7.3 names all four and makes the point that every object you create has exactly one of them, whether you thought about it or not.

8. What to Actually Do

Ask the two questions separately. "Can I write this name here?" and "does the object exist when this runs?" have different answers and different failure modes.

Never return a reference to a local. §6 is the derivation; the compiler warns, and it is only a warning.

Treat an address as a location, not an identity. The same address at two moments may be two different objects.

Do not keep a reference past the lifetime of what it names. Chapter 6.5 §5's temporary is the common case: safe during the call, gone after it.

Use a static local when you genuinely want an object to outlive the calls — a cache, a counter — and read Chapter 7.4 before relying on when it is initialized.

When a bug involves a value that is "sometimes right", suspect this. §4's storage reuse is exactly the mechanism that makes a dangling read work until it does not.

9. Mistakes Worth Making Once

Treating scope and lifetime as one idea. Measured both ways: an object alive with no usable name, and two names sharing one piece of storage.

Assuming an object dies when its name goes out of scope. Measured: the static local was alive for all of main, where its name was a compile error.

Assuming a name is invalid once its object is gone. The opposite — the name is fine, which is why the bug compiles.

Returning a reference to a local. -Wreturn-stack-address, and only a warning.

Reading the output of a dangling-reference program as evidence. It is undefined behaviour; the output is not a fact about C++.

Believing an address identifies an object. Measured: two objects, one address.

Expecting the address reuse in §4 to be guaranteed. The lifetime ending is guaranteed; the reuse is what this implementation did.

10. Interview Relevance

This is the module's Critical chapter and the question behind it is usually asked indirectly.

"What is wrong with returning a reference to a local variable?" The weak answer is "it dangles". The strong answer separates the two properties: the reference is a valid name, the object's lifetime ended when the function returned, and using the name is undefined behaviour. Adding that the storage is frequently reused — so the read may appear to work — explains why the bug survives testing.

"What is the difference between scope and lifetime?" Scope is a compile-time property of a name; lifetime is a run-time property of an object. If you can give an example in each direction — a static local for an object outliving its name, and storage reuse for a name outliving its object — you have demonstrated that you hold two ideas rather than one.

"Why does this bug sometimes work?" Because the storage still exists and may still hold the old value. That answer is what distinguishes someone who has debugged one.

11. What You Should Be Able to Say Now

  1. Scope belongs to a name and is settled at compile time; lifetime belongs to an object and is a run-time property.
  2. A lifetime is the interval between an object's initialization and its destruction.
  3. The two are independent axes, and the proof is that both off-diagonal cases exist.
  4. Measured — object outlives name: a static local printed 1, 2, 3 at the same address across three calls, and its name from outside was error: use of undeclared identifier 'calls'.
  5. Measured — name outlives object: two locals in two functions, different names and values, at the same address 0x16d98689c.
  6. The reason is that the first object's lifetime ended when its function returned, freeing the storage.
  7. An address identifies a location, not an object. One location can host many objects in succession.
  8. The address reuse is what this implementation did; the lifetime ending is what the standard requires.
  9. All four quadrants: ordinary local in use; static local seen from elsewhere; a reference to a destroyed object; an ordinary local after its block.
  10. Only the third lets you write a name and get something wrong — the other three are either safe or rejected.
  11. A dangling reference is that quadrant: a valid name for an object whose lifetime has ended, and using it is undefined behaviour.
  12. It often appears to work because the storage survives and may still hold the value — which is why §4 matters.
  13. Watching a lifetime end needs a destructor and is Chapter 12.7; this chapter infers it from persistence and reuse, and says so.
  14. Running the dangling program and reporting its output would be evidence of nothing — Chapter 5.4 §5 is the cautionary case.

12. Exercises

Exercise 1 — name the quadrant. For each, give the quadrant from §5: (a) a loop index inside its loop (b) a static local, as seen from main (c) a reference returned from a function, at the call site (d) a parameter after the function returns (e) a const& parameter during the call.

Exercise 2 — predict the output. For §3's program with a fourth counter() call added, give all four lines including what the addresses do.

Exercise 3 — explain the shared address. §4 printed one address for two objects. Say what guarantees that this is legal, and what is not guaranteed about it.

Exercise 4 — derive the rule. Without using the word "dangling", explain from §1's two definitions why int& f() { int local = 5; return local; } is a defect.

Exercise 5 — why it passes testing. A colleague's function returns a reference to a local and their tests pass. Explain, with reference to §4, why that is expected and why it is not reassuring.

Exercise 6 — separate the questions. For each statement, say whether it is about scope, lifetime, or both: (a) "calls is not declared here" (b) "calls still holds 3" (c) "the object is destroyed at the closing brace" (d) "the inner declaration hides the outer one" (e) "the storage was reused by the next call".

Exercise 7 — what you cannot see yet. Say what kind of type you would need to observe an object's destruction directly, which chapter supplies it, and why this chapter used persistence and address reuse instead.

13. Summary

  • Scope is a compile-time property of a name; lifetime is a run-time property of an object.
  • They are independent, and both off-diagonal cases are real.
  • Measured: a static local alive at one address across three calls, with its name an error outside the function.
  • Measured: two locals in two functions at the same address — the first's lifetime had ended.
  • An address is a location, not an identity.
  • The reuse is implementation behaviour; the lifetime ending is the guarantee.
  • Four quadrants, and only name in scope, object dead lets you write something wrong.
  • A dangling reference is exactly that quadrant — a valid name, a destroyed object, undefined behaviour.
  • It often appears to work because the storage survives — which is why it escapes testing.
  • Observing destruction directly needs a destructor (12.7); this chapter says so rather than running a UB program.

This chapter answered "when does it exist" three times — for a local, for a static local, and for an object created before main — and treated those as three separate facts. They are not. The standard gives each object exactly one of four storage durations, and the answer to "when does it exist" follows from which one it has rather than from where it was written. The next chapter names all four, and the measurement that makes them concrete is a single program printing one address per category: the two static objects landed four bytes apart, the thread-local one in an entirely different region, and three nested automatic objects marched downward in even steps — one per call.

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.