Scope, duration, and linkage Summary
Review and test your understanding of all scope, duration, and linkage concepts covered in this chapter.
What Are Scope, Duration, and Linkage?
Scope, duration, and linkage are three separate properties that every declaration carries. Scope is about the source code: which regions of text may spell the name. Duration is about the running program: when the object is created and when it is destroyed. Linkage is about the build: whether two declarations of the same spelling refer to one entity or to two unrelated ones.
They get confused with each other because a single keyword often changes two of them at once, and because the most common declarations happen to pair the same answers together. This lesson pulls them apart, then puts them back together as a reference you can return to.
These three properties are independent. A name can be visible in a tiny region of source while the object it names outlives every function call in the program. Nothing about scope predicts duration, and nothing about either one predicts linkage.
Three Independent Questions
Point at any declaration and ask three questions in order. Each has a small, closed set of answers.
| Question | Property | Possible answers |
|---|---|---|
| Where in this file may I write the name? | Scope | Block scope, global scope |
| When does the object come into being and go away? | Duration | Automatic, static, dynamic |
| Can another declaration of this name mean this same entity? | Linkage | No linkage, internal linkage, external linkage |
The rest of the lesson works through the answers one question at a time.
Question One: Where May the Name Be Written?
Scope answers a question about text: over which stretch of the file does this name mean anything? It is a compile-time, text-level property: it is decided entirely by where the declaration sits, and the compiler enforces it by refusing to recognise the name anywhere else.
Block (local) scope runs from the point of declaration to the closing brace of the enclosing block, nested blocks included. Three things get block scope:
- local variables
- function parameters
- program-defined types, such as enumerations and classes, declared inside a block
Global scope runs from the point of declaration to the end of the file. Three things get global scope:
- global variables
- functions
- program-defined types declared at global scope or inside a namespace
Because a name in global scope is still limited to one file's worth of text, seeing a name in another file requires a second declaration there. Whether that second declaration refers to the same entity is question three, not question one.
Question Two: How Long Does the Object Live?
A variable's duration determines when it is created and destroyed. Duration is a runtime property, and it is decided by the storage the variable is given rather than by where its name appears.
- Automatic duration: created at the point of definition, destroyed when the enclosing block exits. Local variables and function parameters have automatic duration by default.
- Static duration: created once when the program starts, destroyed when the program ends. Global variables and static local variables have static duration.
- Dynamic duration: created and destroyed on request, at whatever moments the programmer chooses. Dynamically allocated variables have dynamic duration. A later chapter covers how to request and release that storage.
Question Three: Which Declarations Mean the Same Entity?
An identifier's linkage determines whether declarations of that identifier in different scopes refer to the same entity, meaning the same object, function, or reference. Linkage is a link-time property, which is why it is the one that reaches past the boundaries of a single file.
- No linkage: every declaration introduces its own separate entity. Local variables have no linkage, as do program-defined type identifiers declared inside a block. Two functions may each declare a local named
totaland the two are unrelated variables. - Internal linkage: declarations of the name inside one translation unit refer to the same entity, and other translation units cannot reach it at all. Static global variables get internal linkage whether or not they are initialized, as do static functions, const global variables, and unnamed namespaces along with everything declared inside them.
- External linkage: declarations of the name anywhere in the program refer to the same entity. Non-static functions get external linkage, as do non-const global variables (initialized or not), extern const global variables, inline const global variables, and namespaces.
Functions are external by default, which is why calling a function defined in another file works with nothing more than a forward declaration. Writing static in front of a function definition switches it to internal.
External linkage is what makes a duplicate definition a linker error. If two source files each compile a definition of the same externally linked name, the program violates the one-definition rule and the link fails. Types, templates, and inline functions and variables are the exceptions, and later lessons explain why each of them is allowed more than one definition.
All Three Properties in One Program
The program below declares three variables with three different combinations of answers. g_rainfallTotal is a global: global scope, static duration, external linkage. s_showerCount is a static local: block scope, static duration, no linkage. meanPerShower is an ordinary local: block scope, automatic duration, no linkage.
#include <iostream>
int g_rainfallTotal{ 0 };
void recordShower(int millimetres)
{
static int s_showerCount{ 0 };
++s_showerCount;
g_rainfallTotal += millimetres;
int meanPerShower{ g_rainfallTotal / s_showerCount };
std::cout << "shower " << s_showerCount
<< ": running total " << g_rainfallTotal
<< " mm, mean " << meanPerShower << " mm" << '\n';
}
int main()
{
recordShower(12);
recordShower(7);
recordShower(23);
return 0;
}
shower 1: running total 12 mm, mean 12 mm
shower 2: running total 19 mm, mean 9 mm
shower 3: running total 42 mm, mean 14 mm
The two counters behave identically across calls, since both have static duration and both keep accumulating. What separates them is scope and linkage, neither of which is visible in the output.
Scope Is Not Duration
s_showerCount survives every call to recordShower(), but its name stops working at the function's closing brace. The following program is broken and will not compile, because main() tries to read a name that is not in scope there:
#include <iostream>
void recordShower(int millimetres)
{
static int s_showerCount{ 0 };
++s_showerCount;
std::cout << "shower " << s_showerCount << ": " << millimetres << " mm" << '\n';
}
int main()
{
recordShower(12);
std::cout << s_showerCount << '\n';
return 0;
}
GCC rejects the line in main() with an error reporting that s_showerCount was not declared in this scope. The object exists at that moment in the program's life, holding the value 1, and there is still no legal way to name it from here. That gap between "exists" and "can be named" is exactly why duration and scope are tracked separately.
Linkage Is Not Scope
Linkage is the property that decides what a second declaration in a second file means. Both globals below have global scope in their own file, and only one of them is reachable from elsewhere.
gauge.cpp:
int g_gaugeReading{ 47 };
static int calibrationOffset()
{
return 3;
}
int calibratedReading()
{
return g_gaugeReading + calibrationOffset();
}
main.cpp:
#include <iostream>
extern int g_gaugeReading;
int calibratedReading();
int main()
{
std::cout << "raw reading: " << g_gaugeReading << " mm" << '\n';
std::cout << "calibrated reading: " << calibratedReading() << " mm" << '\n';
return 0;
}
raw reading: 47 mm
calibrated reading: 50 mm
g_gaugeReading and calibratedReading() are external, so the declarations in main.cpp bind to the definitions in gauge.cpp. calibrationOffset() is static and therefore internal: adding a forward declaration of it to main.cpp would compile, then fail at link time, because the linker is not allowed to look inside another translation unit for an internal name.
This example spans two files, so the in-browser runner cannot build it. Save both files side by side and compile them together, for example `g++ -std=c++20 main.cpp gauge.cpp -o gauge`.
The Combinations You Will Meet
Each declaration below answers all three questions at once. The table is grouped by reach, from names no other declaration can touch, through names confined to one file, to names the whole program shares.
| Reach | Declaration | Scope | Duration | Notes |
|---|---|---|---|---|
| No linkage | int dailyPeak{}; inside a block |
Block | Automatic | The default for a local |
| No linkage | void logDepth(int centimetres) parameter |
Block | Automatic | Behaves as a local |
| No linkage | static int s_totalReadings{}; inside a block |
Block | Static | Value survives every call |
| No linkage | int* samples{ new int{} }; inside a block |
Block | Dynamic | Pointer is a local; the object it points at is not |
| Internal | static int g_sensorId{}; at global scope |
Global | Static | Initialized or uninitialized |
| Internal | const int g_maxDepthCm{ 90 }; at global scope |
Global | Static | Must be initialized |
| Internal | constexpr int g_maxDepthCm{ 90 }; at global scope |
Global | Static | Must be initialized |
| Internal | static void logDepth(int centimetres) |
Global | n/a | Functions have no duration of their own |
| External | int g_sensorId{}; at global scope |
Global | Static | Initialized or uninitialized |
| External | inline int g_activeSensors{}; at global scope |
Global | Static | C++17 and later; one definition per file allowed |
| External | extern const int g_maxDepthCm{ 90 }; |
Global | Static | Must be initialized |
| External | inline constexpr int g_maxDepthCm{ 90 }; |
Global | Static | C++17 and later; must be initialized |
| External | void logDepth(int centimetres) |
Global | n/a | The default for a function |
Read the table sideways rather than down. Moving between the internal and external groups only ever changes one keyword, and it never changes scope or duration.
Declaring Something Defined Elsewhere
A forward declaration introduces a name without defining the entity, so a file can use something another file owns. The declared name follows the ordinary scope rules: written at global scope it is visible to the end of that file, and written inside a block it is visible to the end of that block.
| What you want to reach | Write this | The rule that catches people out |
|---|---|---|
| A function | int flushGutter(int litres); |
Prototype only, no body, and no extern needed |
| A non-const global | extern int g_activeSensors; |
Must have no initializer, or it becomes a definition |
| A const global | extern const int g_maxDepthCm; |
extern is required here, and again no initializer |
| A constexpr global | extern constexpr int g_maxDepthCm; |
Not allowed; constexpr cannot be forward declared |
The asymmetry in the first two rows is worth holding on to. A function declaration is unambiguous already, because the missing body is what distinguishes it from a definition, so extern would add nothing. A variable declaration has no such tell, and extern is what marks it as "defined elsewhere" rather than "defined right here".
A constexpr variable is implicitly const, so it can be forward declared through the const form, extern const int g_maxDepthCm;. What crosses the file boundary is const-ness, not constexpr-ness: through that declaration the variable is usable as a const value at runtime, and not as a compile-time constant.
Writing `extern` on a declaration that also has an initializer defines the variable instead of declaring it. On a non-const global that is redundant and likely to draw a warning; on a const global it is the intended way to give the definition external linkage. The initializer, not the keyword, decides which one you wrote.
The Keywords That Set Duration and Linkage
C++ groups the keywords that dial in duration and linkage under one name, storage class specifiers. static and extern are the two you have already met; four remain active in modern C++.
| Specifier | What it sets | Status |
|---|---|---|
extern |
Static storage duration and external linkage | Active |
static |
Static storage duration and internal linkage | Active |
thread_local |
Thread storage duration, one object per thread | Active |
mutable |
Allows a member to be modified through a const object | Active |
auto |
Automatic storage duration | Dropped in C++11, where the keyword was reused for type deduction |
register |
Automatic storage duration, plus a hint to use a CPU register | Deprecated in C++11, meaning removed in C++17 |
The term itself shows up mostly in standards text and compiler documentation rather than in everyday conversation. It is worth recognising, because it explains why static seems to mean unrelated things in different places: on a local it is setting duration, and on a global it is setting linkage.
When a declaration confuses you, name its three answers out loud before reaching for the keyword reference. Most of the confusion around `static` disappears the moment you notice which of the three questions it is answering in that position.
Looking Forward
Three threads from this lesson get picked up later. Unnamed namespaces are the modern way to give a name internal linkage, and the namespaces lesson covers what they do to the names inside them. Inline variables are what let a header hand the same external constant to every file that includes it. Dynamic duration needs the allocation and deallocation operators, plus the discipline for using them safely, which a later chapter covers in full.
Summary
Every declaration answers three independent questions.
Scope decides where the name may be written. Block scope reaches from the declaration to the end of its block. Global scope reaches from the declaration to the end of the file.
Duration decides when the object exists. Automatic duration spans one pass through a block. Static duration spans the whole program. Dynamic duration spans whatever interval the programmer requests.
Linkage decides which declarations mean the same entity. No linkage keeps every declaration separate. Internal linkage joins declarations within one translation unit. External linkage joins declarations across the whole program.
Locals and parameters answer block, automatic, none. Static locals answer block, static, none, so their value outlives calls that cannot name them. Non-const globals answer global, static, external, and static narrows the last answer to internal. Const and constexpr globals answer global, static, internal, and extern or inline widens the last answer to external.
Reaching a name from another file takes a forward declaration: none for functions, extern with no initializer for non-const and const variables, and nothing at all for constexpr, which cannot be forward declared as constexpr. The keywords that do this work, static and extern among them, are called storage class specifiers because they set storage duration and linkage.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Scope, duration, and linkage Summary - Quiz
Test your understanding of the lesson.
Practice Exercises
Identify Scope and Linkage
Identify the scope, duration, and linkage of different variable declarations.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!