Search code examples
c++constantsglobal-variableslinkagec++-faq

Global variables - When to use static, inline, extern, const, and constexpr


There are plenty of questions and answers relating to C++ global variables, such as:

They all contain small fragments of information, such as how inline variables work in C++17 specifically, etc. Some of them also haven't aged well, since C++17 fundamentally changed the rules by introducing inline variables.

C++20 also introduces modules and module linkage, yet again making most content on StackOverflow out-of-date, or even obsolete.

This Q&A is an attempt at unifying these questions, and at giving readers an overview that considers these version changes.

Questions

  • When do I use static, inline, extern, const, constexpr etc. for global variables?
  • How does the answer change historically (before/after C++17, before/after C++20)?
  • How does the answer change depending on where the global variable is located (header/source/module)?

Solution

  • When do I use static, inline, extern, const, constexpr etc. for global variables?

    0. Overview

    Global Variable Use Case Constants Non-Constants
    local to a single source file
    (i.e. declared and only used in a
    single file, not declared in a header)
    static const,
    static constexpr(C++11), or
    const in anonymous namespace(C++11)
    static, or
    in anonymous namespace(C++11)
    declared, not defined in a header,
    defined in a source file
    extern const in header;
    const in source, or
    constexpr(C++11) in source
    extern in header;
    plain in source
    defined in a header,
    until C++17
    imitate inline with templates
    and const or constexpr(C++11); or
    enum for integers only
    imitate inline with templates
    defined in a header,
    since C++17
    inline const, or
    inline constexpr
    inline
    local to a single module(C++20) const or constexpr;
    optionally inline
    optionally inline
    exported by a module(C++20) export const or
    export inline constexpr
    export;
    optionally inline

    In all cases above, constinit(since C++20) may also be used, but not in combination with constexpr. constinit const has it uses too, and is not the same as constexpr.

    Note: the decision may also change based on whether the global variables are defined/used in dynamically linked libraries, and other factors.

    1. Always use ensure that everything local to one source file has internal linkage

    First of all, if the global variable is declared in, and only used in a single source file, then it must have internal linkage. A global variable has internal linkage when:

    • it is marked static
    • it is in an anonymous namespace(since C++11)
    • it is const (or constexpr(since C++11), since constexpr implies const)

    If it doesn't have internal linkage, then you could easily run into an ODR violation. The example below is ill-formed, no diagnostic required.

    // a.cpp
    int counter = 0;   // FIXME: surround with anonymous namespace, or add 'static'
    // b.cpp
    long counter = 0;  // FIXME: surround with anonymous namespace, or add 'static'
    

    Making the variable inline(since C++17) does not solve this issue. Internal linkage makes it safe, because the two counters would be distinct in each TU.

    2. If something is declared, but not defined in a header, make it extern

    Sometimes, it's not important for everyone to have a definition. For example:

    // log.hpp
    extern std::ofstream log_file;
    // log.cpp
    std::ofstream log_file = open_log_file();
    

    It would be pointless to put the definition of log_file to be in a header, and thus visible everywhere. Very little can be gained in terms of performance, and it would force us to make open_log_file() visible everywhere too.

    Note on extern constexpr(since C++11) or extern const constinit(since C++20)

    Another valid but rare use case is extern constexpr(since C++11) (see this answer), i.e. extern const in a header, constexpr(since C++11) in a source. If available, this is better expressed through const constinit(since C++20) in the source.

    The purpose of this pattern is to avoid dynamic initialization for expensive-to-initialize look-up tables while keeping a header/source split.

    3. If something is defined in a header, make it inline(since C++17), or imitate inline with templates(until C++17)

    Sometimes, it is important to have a definition everywhere, such as for global constants that should be inlined:

    inline constexpr float exponent = 1.25f;
    
    Q: Do I really need inline in combination with constexpr?

    Yes, you do. Consider the following example:

    constexpr float exponent = 1.25f; // OK so far, but dangerous
    
    // note: 'const float&' might seem contrived because you could work with 'float'.
    //       However, templates use const& everywhere, so it's very easy to
    //       run into this case indirectly.
    inline const float& foo(const float& x) {
        // IFNDR if the definition of foo appears in multiple translation units (TUs).
        return std::max(x, exponent);
    }
    

    This program may be ill-formed, no diagnostic required, because exponent has internal linkage (due to being const) and is a distinct object in every TU. Each definition of foo may return a different reference to its own unique exponent, which is a violation of [basic.def.odr] p14.5

    Q: What can I do prior to C++17? inline variables don't exist yet.

    A similar mechanism has always existed in the form of templates.

    // define a wrapper class template with a static data member
    template <typename = void>
    struct helper { static const float exponent; };
    // define the static data member
    template <typename T>
    struct helper<T>::exponent = 1.25f;
    // For convenience, make a reference with internal linkage to it.
    // This is safe from ODR violations because
    // [basic.def.odr] p14.5.2 has a special case for it, and
    // this special case was retroactively applied to all C++ standards
    // in the form of a defect report.
    static constexpr float& exponent = helper<>::exponent;
    // (static const float& prior to C++11)
    

    Alternatively, specifically for scoped(since C++11) and unscoped enumerations, you can put the definition in a header without risk:

    inline constexpr int array_size = 100; // since C++17
    enum { array_size = 100; };            // pre-C++17 alternative
    

    4. Make all global variables const or even constexpr(since C++11) whenever possible

    In addition to the rules in 1., 2., 3., always make things const when they can be. This hugely simplifies ensuring correctness of your program, and enables additional compiler optimizations.

    Q: What do I do if initialization is complicated?

    Sometimes you can't just initialize a global with a simple expression, like here:

    std::array<float, 100> lookup;
    
    void init() {
        for (std::size_t i = 0; i < lookup.size(); ++i) {
            lookup[i] = compute(i);
        }
    }
    

    However, don't late-initialize; instead use an immediately invoked lambda expression (IILE)(since C++11) or a regular function to perform initialization.

    constexpr std::array<float, 100> lookup = [] {
        decltype(lookup) result{};
        /* ... */
        return result;
    }();
    
    Q: What impact does const or constexpr(since C++11) have on linkage?

    Making a global variable const or constexpr(since C++11) gives it internal linkage, but as explained in 3., this doesn't simplify anything for you. You still have to worry about linkage and the ODR.

    5. Static data members work differently

    As seen in 3., static data members might not follow the same rules. They have the same linkage as the class they belong to, with the following consequences:

    • Static data members are quasi-inline in the case of class templates.
    • const does not imply internal linkage for static data members.

    Also, constexpr for static data members implies inline(since C++17).