C++ / a working model

34 / 163   ·   C++14   ·   9 min

Storage duration is not a memory partition

Keep this sentence

C++ specifies four storage durations—automatic, static, thread, and dynamic—and does not require a fixed stack, heap, or executable-file layout. The scope of a variable name, the linkage of a name, the lifetime of an object, and operating-system mappings answer different questions. They must not be mashed into a single address diagram.

In this lesson
  1. Ask first who manages storage at the language level
  2. Judge scope and lifetime separately
  3. OS partitions are only an implementation view
  4. Example
  5. Exercise

Ask first who manages storage at the language level

Ordinary block-scope variables usually have automatic storage duration, and their storage is cleaned up when control leaves the corresponding scope. Storage for objects with static storage duration lasts throughout program execution. A thread_local object has thread storage duration: each thread has its own instance. Management of an object with dynamic storage duration is not directly constrained by the local scope that created it.

These categories describe semantics rather than whether an address is high or low. A local variable may live in a register, or it may be optimized away. A dynamic allocation may come from a memory pool. Only observable program behavior needs to match the standard. Answering that local variables always live on the stack over-commits to an implementation strategy. That claim is especially unsuitable for deriving how long an object may legally be accessed.

Judge scope and lifetime separately

The name of a local static is visible only inside the block, but the object is not destroyed every time the function returns. Required dynamic initialization is completed the first time control passes through the declaration. After that succeeds, later calls reuse the same object. C++11 guarantees that this initialization is concurrency-safe. It does not guarantee that later arbitrary reads and writes of the object are automatically synchronized.

In the example the local unique_ptr itself has automatic storage duration, while the integer it manages has dynamic storage duration. Moving the pointer hands management responsibility to the outer object. When the inner variable leaves scope, that integer is not destroyed. This shows that where the pointer lives and how long the object it points to lives are two independent questions.

OS partitions are only an implementation view

A typical program image distinguishes code, read-only data, initialized data, and zero-initialized regions. At run time a thread stack and dynamic mappings are also visible. Those names help debugging, linkers, and performance analysis, but the standard does not require every C++ declaration to land in a fixed segment, and it does not prescribe address order.

const mainly restricts modification at the language level; it is not a request to place an object in a read-only segment. A const local integer can still have automatic storage duration. When diagnosing a problem, first determine semantics from declarations and ownership, then use platform documentation to explain the actual mapping. Do not reverse-engineer a general rule from one printed address.

Pitfalls

  • Thread-safe initialization of a local static does not cover later ++counter; concurrent updates still need synchronization.
  • Leaving a scope destroys only the local raw-pointer object; it does not automatically delete the dynamic object it points to.

Run an example

Minimum C++14 · complete program · Download .cpp

#include <cassert>
#include <memory>
#include <utility>

int next_id() {
    static int value = 0;
    return ++value;
}

int main() {
    std::unique_ptr<int> owner;
    {
        auto local = std::make_unique<int>(42);
        owner = std::move(local);
        assert(!local);
    }
    assert(*owner == 42);
    assert(next_id() == 1);
    assert(next_id() == 2);
}

Compile locally

g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread memory-storage.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

For a function-local static std::vector<int> cache, what properties do the name, the vector object, and the element storage each have?

Show a reference answer

The name cache has block scope. The vector object has static storage duration, and after successful construction its object state is retained across calls. Elements use dynamic storage managed by the vector and may be replaced on reallocation, so the validity of an element pointer must not be equated with cache's storage duration. Clearing ends the lifetime of the elements; the vector object itself still exists.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog