How do inline variables work?

前端 未结 3 1626
一个人的身影
一个人的身影 2020-11-22 07:38

At the 2016 Oulu ISO C++ Standards meeting, a proposal called Inline Variables was voted into C++17 by the standards committee.

In layman\'s terms, what are inline v

3条回答
  •  傲寒
    傲寒 (楼主)
    2020-11-22 08:15

    Minimal runnable example

    This awesome C++17 feature allow us to:

    • conveniently use just a single memory address for each constant
    • store it as a constexpr: How to declare constexpr extern?
    • do it in a single line from one header

    main.cpp

    #include 
    
    #include "notmain.hpp"
    
    int main() {
        // Both files see the same memory address.
        assert(¬main_i == notmain_func());
        assert(notmain_i == 42);
    }
    

    notmain.hpp

    #ifndef NOTMAIN_HPP
    #define NOTMAIN_HPP
    
    inline constexpr int notmain_i = 42;
    
    const int* notmain_func();
    
    #endif
    

    notmain.cpp

    #include "notmain.hpp"
    
    const int* notmain_func() {
        return ¬main_i;
    }
    

    Compile and run:

    g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
    g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
    g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
    ./main
    

    GitHub upstream.

    See also: How do inline variables work?

    C++ standard on inline variables

    The C++ standard guarantees that the addresses will be the same. C++17 N4659 standard draft 10.1.6 "The inline specifier":

    6 An inline function or variable with external linkage shall have the same address in all translation units.

    cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static is not given, then it has external linkage.

    GCC inline variable implementation

    We can observe how it is implemented with:

    nm main.o notmain.o
    

    which contains:

    main.o:
                     U _GLOBAL_OFFSET_TABLE_
                     U _Z12notmain_funcv
    0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                     U __assert_fail
    0000000000000000 T main
    0000000000000000 u notmain_i
    
    notmain.o:
    0000000000000000 T _Z12notmain_funcv
    0000000000000000 u notmain_i
    

    and man nm says about u:

    "u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

    so we see that there is a dedicated ELF extension for this.

    Pre-C++ 17: extern const

    Before C++ 17, and in C, we can achieve a very similar effect with an extern const, which will lead to a single memory location being used.

    The downsides over inline are:

    • it is not possible to make the variable constexpr with this technique, only inline allows that: How to declare constexpr extern?
    • it is less elegant as you have to declare and define the variable separately in the header and cpp file

    main.cpp

    #include 
    
    #include "notmain.hpp"
    
    int main() {
        // Both files see the same memory address.
        assert(¬main_i == notmain_func());
        assert(notmain_i == 42);
    }
    

    notmain.cpp

    #include "notmain.hpp"
    
    const int notmain_i = 42;
    
    const int* notmain_func() {
        return ¬main_i;
    }
    

    notmain.hpp

    #ifndef NOTMAIN_HPP
    #define NOTMAIN_HPP
    
    extern const int notmain_i;
    
    const int* notmain_func();
    
    #endif
    

    GitHub upstream.

    Pre-C++17 header only alternatives

    These are not as good as the extern solution, but they work and only take up a single memory location:

    A constexpr function, because constexpr implies inline and inline allows (forces) the definition to appear on every translation unit:

    constexpr int shared_inline_constexpr() { return 42; }
    

    and I bet that any decent compiler will inline the call.

    You can also use a const or constexpr static integer variable as in:

    #include 
    
    struct MyClass {
        static constexpr int i = 42;
    };
    
    int main() {
        std::cout << MyClass::i << std::endl;
        // undefined reference to `MyClass::i'
        //std::cout << &MyClass::i << std::endl;
    }
    

    but you can't do things like taking its address, or else it becomes odr-used, see also: https://en.cppreference.com/w/cpp/language/static "Constant static members" and Defining constexpr static data members

    C

    In C the situation is the same as C++ pre C++ 17, I've uploaded an example at: What does "static" mean in C?

    The only difference is that in C++, const implies static for globals, but it does not in C: C++ semantics of `static const` vs `const`

    Any way to fully inline it?

    TODO: is there any way to fully inline the variable, without using any memory at all?

    Much like what the preprocessor does.

    This would require somehow:

    • forbidding or detecting if the address of the variable is taken
    • add that information to the ELF object files, and let LTO optimize it up

    Related:

    • C++11 enum with class members and constexpr link-time optimization

    Tested in Ubuntu 18.10, GCC 8.2.0.

提交回复
热议问题