C++, End to End
Scope, Duration, and Linkage
Three separate, orthogonal properties govern every named entity in a C++ program: scope (where the name is visible), storage duration (how long the object actually lives in memory), and linkage (whether the same name in a different file refers to the same entity). They get conflated constantly, and keeping them separate in your head clears up a lot of confusion later.
Scope: where a name is visible
A name declared inside a block ({ }) has block (local) scope: visible from its declaration to the end of that enclosing block, and invisible outside it. A name declared at file scope, outside any function, has global scope: visible from its declaration to the end of the file (and beyond, if declared extern and linked to from elsewhere, see linkage below).
Nested blocks can shadow an outer name: a local variable with the same name as a global (or an outer local) temporarily hides it for the rest of that inner block. This compiles, and most compilers warn about it with -Wshadow, precisely because it's a frequent source of "why did that use the wrong value" bugs.
Storage duration: how long an object actually lives
- Automatic duration: the default for local variables. Created when execution reaches the declaration, destroyed when the enclosing block ends. This is what makes a local variable's address unsafe to return or store past the function's lifetime.
- Static duration: created once, before main runs, and destroyed once, after main returns. Global variables always have this. A local variable can opt into it with the static keyword, in which case it keeps its value between calls instead of being recreated each time.
- Dynamic duration: created explicitly with new, destroyed explicitly with delete (or, in modern C++, managed automatically by a smart pointer, covered in a much later chapter). This is the only duration where you, not the compiler, are responsible for ending the object's life.
Returning a reference or pointer to a local variable is a classic bug directly caused by mixing up scope and storage duration: the name goes out of scope at the closing brace, and the automatic-duration object it referred to is destroyed at that exact same point, so the returned reference/pointer is left dangling.
Try it yourself: run the dangling-reference example above and read the actual compiler warning it produces (most compilers do catch this specific case). Then fix it by returning by value instead of by reference, and notice the warning disappears because a returned value is copied out before the local is destroyed.
Internal versus external linkage
Linkage only applies to names at file scope (globals, free functions), never to locals. A name with external linkage refers to the same single entity across every translation unit that declares it (this is what lets main.cpp call a function defined in a different .cpp file, as in the previous chapter). A name with internal linkage is private to its own translation unit: the same name in a different file is a completely separate, unrelated entity, even if it happens to be spelled identically.
// file_a.cpp
static int counter = 0; // internal linkage: this counter is private to file_a.cpp
int sharedValue = 10; // external linkage (the default for non-const globals):
// visible to other files via an extern declarationstatic, applied to a global variable or a free function (not a class member, where static means something else entirely, covered in the classes chapters), gives it internal linkage. It's a genuinely useful tool for a helper function or a variable that's an implementation detail of one .cpp file and should never accidentally collide with a same-named symbol somewhere else in the program. const globals get internal linkage by default in C++, unlike in C, specifically because giving every const global external linkage by default would make simple header constants a linker-error minefield.
Namespaces
A namespace groups related names under a common prefix, which is the language's actual mechanism for avoiding name collisions across a large program or between separate libraries ("my_math::add" and "their_math::add" can coexist without conflict, where two bare global "add" functions could not).
Namespaces can nest (geometry::shapes::circleArea), and can be reopened across multiple files, letting a single logical namespace span an entire library. An anonymous namespace (namespace { ... }, no name) gives everything inside it internal linkage automatically, and in modern C++ it's generally preferred over scattering static in front of individual declarations, since it applies uniformly to everything inside without repetition.
The static initialization order fiasco
Global variables with static duration are initialized before main runs, but the standard only guarantees a specific order for globals within the same translation unit (in declaration order). Across different translation units, the order is unspecified. If one global's initializer depends on another global defined in a different .cpp file, there's a real chance it runs before that other global has been initialized, silently reading a zero-initialized (or otherwise not-yet-set) value.
// file_a.cpp
int a = 10;// file_b.cpp
extern int a;
int b = a * 2; // UNDEFINED whether a is 10 or 0 here, depending on link order,
// which depends on your build system, not your source codeThis is exactly the kind of bug that works fine for months and then breaks the moment someone reorders source files in a build script or switches compilers. The practical fix, when a global genuinely must depend on another global's value, is to wrap it in a function returning a local static instead: a local static is guaranteed to be initialized the first time control passes through its declaration, which sidesteps the whole cross-file ordering problem.
Try it yourself: this one is worth reading rather than running, since the bug it describes is nondeterministic by nature. Write down, in your own words, why a local static sidesteps the cross-file ordering problem that a plain global doesn't. If you can explain why, you understand the actual mechanism, not just the workaround.