인라인 변수는 어떻게 작동합니까? 표준위원회에 의해 C ++ 17로 투표되었습니다. 평신도의 관점에서

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;
}

이 접근 방식에는 주로 성능 측면에서 몇 가지 단점이 있습니다. 이 오버 헤드는 템플릿 솔루션으로 피할 수 있지만 잘못 이해하기 쉽습니다.

인라인 변수를 사용하면 다중 정의 링커 오류없이 직접 선언 할 수 있습니다.

inline T global;

헤더 전용 라이브러리 외에도 인라인 변수가 도움이 될 수있는 다른 경우가 있습니다. Nir Friedman은 CppCon : C ++ 개발자가 글로벌 (및 링커)에 대해 알아야 할 사항 에서이 주제를 다룹니다 . 인라인 변수 및 해결 방법에 대한 부분은 18m9s에서 시작됩니다 .

간단히 말해서, 컴파일 단위간에 공유되는 전역 변수를 선언해야하는 경우 헤더 파일에서 인라인 변수로 선언하는 것은 간단하며 C ++ 17 이전 해결 방법의 문제를 피할 수 있습니다.

(예를 들어 명시 적으로 지연 초기화를 원할 경우 Meyer의 싱글 톤에 대한 사용 사례가 여전히 있습니다.)


답변

최소 실행 가능 예

이 멋진 C ++ 17 기능을 통해 다음을 수행 할 수 있습니다.

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;
}

컴파일 및 실행 :

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 업스트림 .

참고 항목 : 인라인 변수는 어떻게 작동합니까?

인라인 변수에 대한 C ++ 표준

C ++ 표준은 주소가 동일하다는 것을 보장합니다. C ++ 17 N4659 표준 초안
10.1.6 “인라인 지정자”:

6 외부 연결이있는 인라인 함수 또는 변수는 모든 변환 단위에서 동일한 주소를 가져야합니다.

cppreference https://en.cppreference.com/w/cpp/language/inline는 경우라고 설명 static지정되어 있지 않은 경우, 그것은 외부 링크가 있습니다.

GCC 인라인 변수 구현

우리는 그것이 어떻게 구현되는지 관찰 할 수 있습니다 :

nm main.o notmain.o

포함하는:

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

그리고 man nm에 대해 말한다 u:

“u”기호는 고유 한 글로벌 기호입니다. 이것은 ELF 심볼 바인딩의 표준 세트에 대한 GNU 확장입니다. 이러한 심볼의 경우 동적 링커는 전체 프로세스에서이 이름과 유형이 사용중인 심볼이 하나만 있는지 확인합니다.

이를위한 전용 ELF 확장이 있음을 알 수 있습니다.

C ++ 이전 17 : extern const

C ++ 17 이전과 C에서는를 사용하여 매우 유사한 효과를 얻을 수 있으며 extern const,이 경우 단일 메모리 위치가 사용됩니다.

단점 inline은 다음과 같습니다.

  • constexpr이 기술로 변수를 만드는 것은 불가능합니다 . 단지 다음을 inline허용합니다. constexpr extern을 선언하는 방법?
  • 헤더와 cpp 파일에서 별도로 변수를 선언하고 정의해야하므로 덜 우아합니다.

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 업스트림 .

Pre-C ++ 17 헤더 전용 대안

이들은 extern솔루션 만큼 좋지는 않지만 작동하며 단일 메모리 위치 만 차지합니다.

constexpr때문에 기능, constexpr의미inlineinline 수 (힘) 정의는 모든 번역 단위에 표시합니다 :

constexpr int shared_inline_constexpr() { return 42; }

괜찮은 컴파일러가 호출을 인라인 할 것이라고 장담합니다.

다음 과 같이 const또는 constexpr정적 정수 변수를 사용할 수도 있습니다 .

#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;
}

그러나 주소를 가져 오는 것과 같은 작업을 수행 할 수 없습니다. 그렇지 않으면 odr이 사용됩니다. https://en.cppreference.com/w/cpp/language/static “Constant static members”및 constexpr 정적 데이터 정의 도 참조하십시오. 회원

C에서 상황은 C ++ 이전 C ++ 17과 동일합니다. 다음 위치에 예제를 업로드했습니다. C에서 “정적”은 무엇을 의미합니까?

유일한 차이점은 C ++에서는 전역을 const의미 static하지만 C에서는 그렇지 않다는 것입니다 .`static const` 대`const`의 C ++ 의미 체계

완전히 인라인 할 방법이 있습니까?

TODO : 메모리를 전혀 사용하지 않고 변수를 완전히 인라인 할 수있는 방법이 있습니까?

전처리 기가하는 것과 매우 비슷합니다.

이것은 어떻게 든 필요합니다.

  • 변수의 주소가 사용되는지 여부를 금지하거나 감지
  • 해당 정보를 ELF 개체 파일에 추가하고 LTO가이를 최적화하도록합니다.

관련 :

Ubuntu 18.10, GCC 8.2.0에서 테스트되었습니다.