programing

인라인 변수는 어떻게 작동합니까?

nasanasas 2020. 8. 18. 07:47
반응형

인라인 변수는 어떻게 작동합니까?


2016 Oulu ISO C ++ 표준 회의에서 Inline Variables 라는 제안 이 표준위원회에 의해 C ++ 17로 투표되었습니다.

평신도의 관점에서 인라인 변수는 무엇이며 어떻게 작동하며 어떤 용도로 유용합니까? 인라인 변수는 어떻게 선언, 정의 및 사용되어야합니까?


제안의 첫 문장 :

"inline 지정자 변수뿐만 아니라 기능에 적용 할 수있다.

inline기능에 적용되는 ¹ 보장 된 효과는 여러 번역 단위에서 외부 연결을 사용하여 기능을 동일하게 정의 할 수 있도록하는 것입니다. 실습에서는 여러 번역 단위에 포함될 수있는 헤더에 함수를 정의하는 것을 의미합니다. 제안은 이러한 가능성을 변수로 확장합니다.

따라서 실질적인 측면에서 제안은 inline키워드를 사용하여 헤더 파일에서 외부 연결 const네임 스페이스 범위 변수 또는 static클래스 데이터 멤버를 정의하여 해당 헤더가 포함되었을 때 발생하는 여러 정의가 링커에서는 여러 번역 단위를 사용할 수 있습니다. 하나만 선택 하면됩니다.

C ++ 14를 포함하기 전까지 static는 클래스 템플릿에서 변수 를 지원하기위한 내부 기계가 있었지만 그 기계를 사용하는 편리한 방법이 없었습니다. 하나는 같은 트릭에 의지해야

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

C ++ 17 이후부터는 다음과 같이 작성할 수 있습니다.

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

… 헤더 파일에서.

제안에는 문구가 포함됩니다.

인라인 정적 데이터 멤버는 클래스 정의에서 정의 할 수 있으며 중괄호 또는 같음 이니셜 라이저를 지정할 수 있습니다. 멤버가 constexpr지정자로 선언 된 경우 이니셜 라이저없이 네임 스페이스 범위에서 다시 선언 될 수 있습니다 (이 사용법은 더 이상 사용되지 않음; 참조 ‌ DX). 다른 정적 데이터 멤버의 선언은 중괄호 또는 같음 이니셜 라이저를 지정하지 않아야합니다.

… 위의 내용을 더 단순화하여

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

… TC 가이 답변 대한 의견 에서 언급 한대로 .

또한,  ​constexpr지정자는 inline 함수뿐만 아니라 정적 데이터 멤버를 의미  합니다.


참고 :
¹ 함수의 경우 inline최적화에 대한 암시 효과도 있습니다. 컴파일러는이 함수 호출을 함수의 기계어 코드를 직접 대체하는 것을 선호해야합니다. 이 힌트는 무시할 수 있습니다.


인라인 변수는 인라인 함수와 매우 유사합니다. 변수가 여러 컴파일 단위에 표시 되더라도 변수의 인스턴스가 하나만 있어야 함을 링커에 알립니다. 링커는 더 이상 복사본이 생성되지 않도록해야합니다.

인라인 변수는 헤더 전용 라이브러리에서 전역을 정의하는 데 사용할 수 있습니다. C ++ 17 이전에는 해결 방법 (인라인 함수 또는 템플릿 해킹)을 사용해야했습니다.

예를 들어 한 가지 해결 방법은 인라인 함수와 함께 Meyer의 싱글 톤을 사용하는 것입니다.

inline T& instance()
{
  static T global;
  return global;
}

There are some drawbacks with this approach, mostly in terms of performance. This overhead could be avoided by template solutions, but it is easy to get them wrong.

With inline variables, you can directly declare it (without getting a multiple definition linker error):

inline T global;

Apart from header only libraries, there other cases where inline variables can help. Nir Friedman covers this topic in his talk at CppCon: What C++ developers should know about globals (and the linker). The part about inline variables and the workarounds starts at 18m9s.

Long story short, if you need to declare global variables that are shared between compilation units, declaring them as inline variables in the header file is straightforward and avoids the problems with pre-C++17 workarounds.

(There are still use cases for the Meyer's singleton, for instance, if you explicitely want to have lazy initialization.)


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 <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_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 &notmain_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 <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_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 <iostream>

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:

Tested in Ubuntu 18.10, GCC 8.2.0.

참고URL : https://stackoverflow.com/questions/38043442/how-do-inline-variables-work

반응형