Jak działają zmienne wbudowane?

124

Na spotkaniu Oulu ISO C ++ w 2016 r ., Komitet normalizacyjny przegłosował propozycję o nazwie Inline Variables w języku C ++ 17.

Mówiąc prościej, czym są zmienne wbudowane, jak działają i do czego są przydatne? W jaki sposób należy deklarować, definiować i stosować zmienne wbudowane?

jotik
źródło
@jotik Wydaje mi się, że równoważną operacją byłoby zastąpienie dowolnego wystąpienia zmiennej jej wartością. Zwykle jest to ważne tylko wtedy, gdy zmienna to const.
melpomene
5
To nie jedyna rzecz, jaką inlinesłowo kluczowe robi dla funkcji. Słowo inlinekluczowe zastosowane do funkcji ma jeszcze jeden istotny skutek, który przekłada się bezpośrednio na zmienne. inlineFunkcja, która jest przypuszczalnie zadeklarowana w pliku nagłówkowym, nie spowoduje „duplikat symbol” błędy w czasie łącza, nawet jeśli nagłówek dostaje #included przez wielu jednostek tłumaczeniowych. Słowo inlinekluczowe zastosowane do zmiennych da dokładnie ten sam wynik. Koniec.
Sam Varshavchik
4
^ W sensie „zastąpienie dowolnego wywołania tej funkcji lokalną kopią jego kodu” inlinejest tylko słabym, niewiążącym żądaniem skierowanym do optymalizatora. Kompilatory mogą nie wstawiać żądanych funkcji i / lub wbudowywać te, których nie dodałeś adnotacji. Prawdziwym celem inlinesłowa kluczowego jest raczej obejście wielu błędów definicji.
underscore_d

Odpowiedzi:

121

Pierwsze zdanie wniosku:

inline Specifier może być stosowany do zmiennych, jak i funkcji.

Gwarantowanym efektem inlinezastosowania do funkcji jest umożliwienie identycznego zdefiniowania funkcji, z powiązaniami zewnętrznymi, w wielu jednostkach tłumaczeniowych. W praktyce oznacza to zdefiniowanie funkcji w nagłówku, która może być zawarta w wielu jednostkach tłumaczeniowych. Wniosek rozszerza tę możliwość na zmienne.

Tak więc, w praktyce (teraz zaakceptowana) propozycja umożliwia użycie inlinesłowa kluczowego do zdefiniowania constzmiennej zakresu przestrzeni nazw powiązania zewnętrznego lub dowolnego staticelementu członkowskiego danych klasy w pliku nagłówkowym, dzięki czemu wiele definicji, które powstają, gdy ten nagłówek zostanie uwzględniony w Wiele jednostek tłumaczeniowych jest w porządku z linkerem - po prostu wybiera jedną z nich.

Aż do C ++ 14 włącznie wewnętrzny mechanizm do tego służył, aby wspierać staticzmienne w szablonach klas, ale nie było wygodnego sposobu używania tego mechanizmu. Trzeba było uciekać się do takich sztuczek

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`.

Od C ++ 17 i nowszych uważam, że można po prostu pisać

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

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

… W pliku nagłówkowym.

Wniosek zawiera sformułowanie

Wbudowany statyczny element członkowski danych można zdefiniować w definicji klasy i może określać inicjator nawiasów klamrowych lub równych. Jeśli element członkowski jest zadeklarowany ze constexprspecyfikatorem, może zostać ponownie zadeklarowany w zakresie przestrzeni nazw bez inicjatora (to użycie jest przestarzałe; patrz‌ DX). Deklaracje innych statycznych elementów danych nie mogą określać nawiasu klamrowego lub równego in‌ itializera

… Co pozwala na dalsze uproszczenie powyższego do just

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

… Jak zauważył TC w komentarzu do tej odpowiedzi.

Ponadto  ​constexprspecyfikator implikuje  inline statyczne elementy składowe danych, a także funkcje.


Uwagi:
¹ Ponieważ funkcja inlinema również wpływ na optymalizację, kompilator powinien preferować zamianę wywołań tej funkcji na bezpośrednie podstawienie kodu maszynowego funkcji. Tę wskazówkę można zignorować.

Pozdrawiam i hth. - Alf
źródło
2
Ponadto ograniczenie const dotyczy tylko zmiennych zakresu przestrzeni nazw. Te z zakresem klas (jak Kath::hi) nie muszą być stałymi.
TC
4
Nowsze raporty wskazują, że constograniczenie zostało całkowicie zniesione.
TC,
2
@Nick: Ponieważ Richard Smith (obecny „redaktor projektu” komitetu C ++) jest jednym z dwóch autorów, a ponieważ jest „właścicielem kodu interfejsu użytkownika Clang C ++”, zgadł Clang. A konstrukcja skompilowana z clang 3.9.0 w Godbolt . Ostrzega, że ​​zmienne wbudowane są rozszerzeniem C ++ 1z. Nie znalazłem sposobu na udostępnienie źródła i wyboru kompilatora oraz opcji, więc link jest tylko do strony w ogóle, przepraszam.
Pozdrawiam i hth. - Alf
1
po co wbudowane słowo kluczowe w deklaracji klasy / struktury? Dlaczego po prostu nie pozwalasz static std::string const hi = "Zzzzz...";?
sasha.sochka
2
@EmilianCioca: Nie, wpadłbyś w konflikt z fiaskiem statycznej kolejności inicjalizacji . Singleton jest zasadniczo sposobem na uniknięcie tego.
Pozdrawiam i hth. - Alf
15

Zmienne wbudowane są bardzo podobne do funkcji wbudowanych. Sygnalizuje linkerowi, że powinno istnieć tylko jedno wystąpienie zmiennej, nawet jeśli zmienna jest widoczna w wielu jednostkach kompilacji. Konsolidator musi upewnić się, że nie są tworzone więcej kopii.

Zmienne wbudowane mogą służyć do definiowania zmiennych globalnych w bibliotekach zawierających tylko nagłówki. Przed C ++ 17 musieli stosować obejścia (funkcje wbudowane lub hacki szablonów).

Na przykład jednym obejściem jest użycie singletona Meyera z funkcją wbudowaną:

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

Takie podejście ma pewne wady, głównie w zakresie wydajności. Tego narzutu można by uniknąć dzięki rozwiązaniom opartym na szablonach, ale łatwo jest je pomylić.

Dzięki zmiennym wbudowanym możesz je bezpośrednio zadeklarować (bez otrzymania błędu konsolidatora z wieloma definicjami):

inline T global;

Oprócz bibliotek zawierających tylko nagłówki, istnieją inne przypadki, w których mogą pomóc zmienne wbudowane. Nir Friedman omawia ten temat w swoim wykładzie na CppCon: Co programiści C ++ powinni wiedzieć o globals (i konsolidatorze) . Część dotycząca zmiennych wbudowanych i obejść zaczyna się od 18m9s .

Krótko mówiąc, jeśli musisz zadeklarować zmienne globalne, które są wspólne dla jednostek kompilacji, zadeklarowanie ich jako zmiennych wbudowanych w pliku nagłówkowym jest proste i pozwala uniknąć problemów z obejściami przed C ++ 17.

(Na przykład nadal istnieją przypadki użycia singletona Meyera, jeśli jawnie chcesz mieć leniwą inicjalizację).

Philipp Claßen
źródło
11

Minimalny działający przykład

Ta niesamowita funkcja C ++ 17 pozwala nam:

  • wygodnie używać tylko jednego adresu pamięci dla każdej stałej
  • zapisz to jako constexpr: Jak zadeklarować extern constexpr?
  • zrób to w jednej linii z jednego nagłówka

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

Skompiluj i uruchom:

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 .

Zobacz także: Jak działają zmienne wbudowane?

Standard C ++ dotyczący zmiennych wbudowanych

Standard C ++ gwarantuje, że adresy będą takie same. C ++ 17 N4659 standardowa wersja robocza 10.1.6 „Specyfikator wbudowany”:

6 Funkcja lub zmienna wbudowana z połączeniem zewnętrznym ma ten sam adres we wszystkich jednostkach tłumaczeniowych.

cppreference https://en.cppreference.com/w/cpp/language/inline wyjaśnia, że ​​jeśli staticnie jest podane, ma link zewnętrzny.

Implementacja zmiennych inline GCC

Możemy obserwować, jak jest realizowany za pomocą:

nm main.o notmain.o

który zawiera:

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

i man nmmówi o u:

„u” Symbol jest unikalnym symbolem globalnym. To jest rozszerzenie GNU do standardowego zestawu powiązań symboli ELF. Dla takiego symbolu dynamiczny linker upewni się, że w całym procesie jest tylko jeden symbol o tej nazwie i typie w użyciu.

więc widzimy, że jest do tego dedykowane rozszerzenie ELF.

Przed C ++ 17: extern const

Przed C ++ 17, a także w C, możemy osiągnąć bardzo podobny efekt za pomocą extern const, co doprowadzi do użycia jednej lokalizacji pamięci.

Wady inlineto:

  • nie jest możliwe utworzenie zmiennej constexprtą techniką, inlinepozwala tylko na to: Jak zadeklarować constexpr extern?
  • jest mniej elegancki, ponieważ musisz zadeklarować i zdefiniować zmienną oddzielnie w nagłówku i pliku 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 upstream .

Alternatywy tylko dla nagłówków przed C ++ 17

Nie są one tak dobre, jak externrozwiązanie, ale działają i zajmują tylko jedną lokalizację w pamięci:

constexprFunkcja, ponieważ constexprzakładainline i inline pozwala (siły) definicja pojawiać się na każdej jednostki tłumaczeniowej :

constexpr int shared_inline_constexpr() { return 42; }

i założę się, że każdy przyzwoity kompilator wbuduje wywołanie.

Można również użyć constlub constexprstatyczną zmienną całkowitą, na przykład:

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

ale nie możesz robić takich rzeczy, jak pobieranie jego adresu, bo inaczej zostanie użyty odr, zobacz także: https://en.cppreference.com/w/cpp/language/static "Stałe statyczne składowe " i Definiowanie stałych danych statycznych członków

do

W C sytuacja jest taka sama, jak w C ++ przed C ++ 17, przesłałem przykład pod adresem: Co oznacza „statyczny” w C?

Jedyną różnicą jest to, że w C ++ constimplikuje to staticdla globals, ale nie w C: C ++ semantyka `static const` vs` const`

Jakikolwiek sposób, aby w pełni go wbudować?

DO ZROBIENIA: czy istnieje sposób na pełne wstawienie zmiennej bez użycia jakiejkolwiek pamięci?

Podobnie jak robi to preprocesor.

Wymagałoby to w jakiś sposób:

  • zakazanie lub wykrywanie, czy adres zmiennej jest brany
  • dodaj te informacje do plików obiektowych ELF i pozwól LTO zoptymalizować je

Związane z:

Testowany w Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
2
inlinenie ma prawie nic wspólnego z wstawianiem, ani dla funkcji, ani dla zmiennych, pomimo samego słowa. inlinenie mówi kompilatorowi, aby cokolwiek wstawiał. Mówi linkerowi, aby upewnił się, że istnieje tylko jedna definicja, która jest tradycyjnie wykonywana przez programistę. Więc "Jakiś sposób, aby to w pełni wbudować?" to przynajmniej zupełnie niezwiązane pytanie.
not-a-user