Learncpp: 02 Scope, Duration, and Linkage

User-Defined Namespaces

  1. C++ allows defining custom namespaces using the namespace keyword.
  2. A namespace must be declared at global scope or inside another namespace.
  3. To access an identifier inside a namespace, use the scope resolution operator (::).
  4. The global scope resolution operator (::) before a function name allows calling the global version of a function explicitly.
  5. Namespace blocks can be declared in multiple locations, including:
    • Across multiple files in a project.
    • In different parts of the same file.
  6. Namespaces can be nested inside other namespaces.
  7. Long namespace names can be shortened using namespace aliases:
    namespace Active = Foo::Goo; // Active now refers to Foo::Goo
    

Local Variables

  1. Scope:
    • Local variables have block scope.
    • They are in scope from their definition to the end of the block they are declared in.
  2. Duration:
    • Local variables have automatic storage duration.
    • They are created at the point of definition and destroyed at the end of the block.
  3. 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

  1. A global variable is a variable declared outside of any function.
  2. It has global namespace scope, meaning it is visible from the point of declaration to the end of the file.
  3. Global variables have static duration, meaning:
    • They are created when the program starts.
    • They are destroyed when the program ends.
  4. Initialization:
    • Unlike local variables, global variables are zero-initialized by default.
    • Constant global variables must be explicitly initialized.
  5. 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.
  6. Best Practices:
    • Developers often prefix global variables with "g_" to indicate they are global.
    • Use the -Wshadow flag (GCC/Clang) to detect shadowed variables.

Internal Linkage

  1. Internal linkage means the identifier is only accessible within the same translation unit.
  2. Global variables and functions can have internal or external linkage.
  3. Local variables always have no linkage.
  4. 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.
  5. 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.
  6. 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

  1. External linkage allows identifiers to be accessed across translation units.
  2. Functions have external linkage by default.
  3. 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)
      
  4. 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.

Why Non-Const Global Variables Are Dangerous

  1. Any function can modify global variables, making code hard to debug.
  2. 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.
    • Dynamic initialization happens later for non-constexpr initialized variables.
  3. Valid Use Cases:
    • Logging systems often use global variables.

Inline Functions and Variables

  1. Function Call Overhead:
    • Function calls involve a performance cost.
    • Inline expansion replaces function calls with their actual code.
  2. Downside of Inline Expansion:
    • If a function is too large, replacing calls with code increases executable size.
  3. Modern inline Keyword:
    • It now means “multiple definitions are allowed” across translation units.
  4. Implicitly Inline Functions:
    • Functions defined inside classes.
    • constexpr or consteval functions.
  5. Why not make all functions inline and defined in a header file? Mainly because doing so can increase your compile times significantly.
  6. Inline Variables (C++17):
    • Allow global variables to be defined in multiple files.

Sharing Global Constants Across Files

  1. 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.
  2. 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

  1. Using the static keyword on a local variable changes its duration from automatic to static.
  2. 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.
  3. 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.
  4. Re-initialization Behavior:
    • A static local variable is initialized only once.
    • On subsequent function calls, the initialization does not happen again.
  5. 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.
  6. 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.



    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Learncpp: 01 Functions and Files
  • Linux Device Drivers: 01 Introduction