Learncpp: 02 Scope, Duration, and Linkage
User-Defined Namespaces
- C++ allows defining custom namespaces using the
namespace
keyword. - A namespace must be declared at global scope or inside another namespace.
- To access an identifier inside a namespace, use the scope resolution operator (
::
). - The global scope resolution operator (
::
) before a function name allows calling the global version of a function explicitly. - Namespace blocks can be declared in multiple locations, including:
- Across multiple files in a project.
- In different parts of the same file.
- Namespaces can be nested inside other namespaces.
- Long namespace names can be shortened using namespace aliases:
namespace Active = Foo::Goo; // Active now refers to Foo::Goo
Local Variables
- Scope:
- Local variables have block scope.
- They are in scope from their definition to the end of the block they are declared in.
- Duration:
- Local variables have automatic storage duration.
- They are created at the point of definition and destroyed at the end of the block.
- Linkage:
- Linkage determines if multiple declarations of an identifier refer to the same object.
- Local variables have no linkage, meaning each declaration of a local variable creates a new unique object.
Global Variables
- A global variable is a variable declared outside of any function.
- It has global namespace scope, meaning it is visible from the point of declaration to the end of the file.
- Global variables have static duration, meaning:
- They are created when the program starts.
- They are destroyed when the program ends.
- Initialization:
- Unlike local variables, global variables are zero-initialized by default.
- Constant global variables must be explicitly initialized.
- Shadowing:
- A local variable with the same name as a global variable will shadow the global variable.
- The scope resolution operator (
::
) can be used to explicitly refer to the global variable.
- Best Practices:
- Developers often prefix global variables with
"g_"
to indicate they are global. - Use the
-Wshadow
flag (GCC/Clang) to detect shadowed variables.
- Developers often prefix global variables with
Internal Linkage
- Internal linkage means the identifier is only accessible within the same translation unit.
- Global variables and functions can have internal or external linkage.
- Local variables always have no linkage.
- Usage:
- Use
static
to give a global variable internal linkage:static int g_x{}; // g_x is internal to this file
- Const and constexpr globals have internal linkage by default.
- Use
- Why Const Has Internal Linkage:
- Const objects must be usable in constant expressions.
- This means the compiler must see a definition, not just a declaration.
- This allows header files to include constants without ODR violations.
- Functions with Internal Linkage:
- Functions default to external linkage.
- They can be made internal using
static
:static void helperFunction() { /* Only accessible in this file */ }
- Unnamed namespaces provide internal linkage without using
static
.
External Linkage and Variable Forward Declarations
- External linkage allows identifiers to be accessed across translation units.
- Functions have external linkage by default.
- Global Variables with External Linkage:
- They are called external variables.
- To explicitly make a global variable external, use
extern
:int g_x { 2 }; // non-constant globals are external by default (no need to use extern) extern const int g_y { 3 }; // const globals can be defined as extern, making them external extern constexpr int g_z { 3 }; // constexpr globals can be defined as extern, making them external (but this is pretty useless)
- Forward Declarations Using
extern
:- Allows referencing variables defined in another file:
extern const int g_y;
- Avoid using
extern
on a non-const variable with an initializer.
- Allows referencing variables defined in another file:
Why Non-Const Global Variables Are Dangerous
- Any function can modify global variables, making code hard to debug.
- Initialization Order Problem:
- Static initialization occurs in two phases:
- Constant initialization: Variables with
constexpr
initializers are initialized first. - Zero-initialization: Uninitialized variables are set to
0
before use.
- Constant initialization: Variables with
- Dynamic initialization happens later for non-constexpr initialized variables.
- Static initialization occurs in two phases:
- Valid Use Cases:
- Logging systems often use global variables.
Inline Functions and Variables
- Function Call Overhead:
- Function calls involve a performance cost.
- Inline expansion replaces function calls with their actual code.
- Downside of Inline Expansion:
- If a function is too large, replacing calls with code increases executable size.
- Modern
inline
Keyword:- It now means “multiple definitions are allowed” across translation units.
- Implicitly Inline Functions:
- Functions defined inside classes.
-
constexpr
orconsteval
functions.
- Why not make all functions inline and defined in a header file? Mainly because doing so can increase your compile times significantly.
- Inline Variables (C++17):
- Allow global variables to be defined in multiple files.
Sharing Global Constants Across Files
- Pre-C++17 Approach:
- Create a header file with a namespace:
namespace constants { constexpr double pi { 3.14159 }; }
- Changing a constant requires recompiling all files.
- Large constants can consume memory.
- Create a header file with a namespace:
- C++17 Inline Variables:
namespace constants { inline constexpr double pi { 3.14159 }; }
- Can be used in constant expressions across multiple files.
- Only one copy of each variable exists.
Static Local Variables
- Using the
static
keyword on a local variable changes its duration from automatic to static. - A static local variable:
- Persists for the entire program lifetime instead of being destroyed at block exit.
- Retains its value between function calls, unlike normal local variables.
- Initialization Rules:
- If zero-initialized or initialized with a
constexpr
value, it is initialized at program start. - If uninitialized or initialized with a non-constexpr value, it is zero-initialized at program start.
- If initialized with a non-constexpr expression, it is initialized only on the first function call.
- If zero-initialized or initialized with a
- Re-initialization Behavior:
- A static local variable is initialized only once.
- On subsequent function calls, the initialization does not happen again.
- Scope vs. Lifetime:
- A static local variable has block scope, like a normal local variable.
- However, its lifetime extends until the program terminates, like a global variable.
- Const and
constexpr
Static Local Variables:- A const static local variable is useful when:
- The value never changes.
- The initialization is expensive (e.g., reading from a file or database).
- This ensures the function only computes the value once, instead of recalculating on every call.
- A const static local variable is useful when:
Enjoy Reading This Article?
Here are some more articles you might like to read next: