wake-up-neo.com

Wie funktionieren Inline-Variablen?

Auf dem ISO-C++ - Normungstreffen in Oulu 2016 wurde ein Vorschlag mit dem Namen Inline Variables vom Normungsausschuss in C++ 17 gewählt.

Was sind Inline-Variablen, wie funktionieren sie und wofür sind sie nützlich? Wie sollen Inline-Variablen deklariert, definiert und verwendet werden?

99
jotik

Der erste Satz des Vorschlags:

Der Bezeichner inline kann sowohl auf Variablen als auch auf Funktionen angewendet werden.

Die ¹Garantiewirkung von inline auf eine Funktion besteht darin, dass die Funktion mit externer Verknüpfung in mehreren Übersetzungseinheiten identisch definiert werden kann. In der Praxis bedeutet dies, die Funktion in einem Header zu definieren, der in mehreren Übersetzungseinheiten enthalten sein kann. Der Vorschlag erweitert diese Möglichkeit auf Variablen.

In der Praxis können Sie also mit dem (jetzt akzeptierten) Vorschlag das Schlüsselwort inline verwenden, um eine externe Verbindungsvariable const für den Namespace oder ein beliebiges Datenelement der Klasse static zu definieren. in einer Header-Datei, sodass die mehreren Definitionen, die sich ergeben, wenn dieser Header in mehreren Übersetzungseinheiten enthalten ist, für den Linker in Ordnung sind - es wird nur eins ausgewählt.

Bis einschließlich C++ 14 war die interne Maschinerie dafür vorhanden, um static Variablen in Klassenvorlagen zu unterstützen, aber es gab keine bequeme Möglichkeit, diese Maschinerie zu verwenden. Man musste auf Tricks wie zurückgreifen

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

Ab C++ 17 glaube ich, dass man einfach schreiben kann

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

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

… In einer Header-Datei.

Der Vorschlag enthält den Wortlaut

Ein statisches Inline-Datenelement kann in der Klassendefinition definiert werden und einen Klammer- oder Gleichheitsinitialisierer angeben. Wenn das Member mit dem constexpr -Spezifizierer deklariert wird, wird es möglicherweise im Namespace-Bereich ohne Initialisierung neu deklariert (diese Verwendung ist veraltet; siehe ‌ D.X). Deklarationen anderer statischer Datenelemente dürfen keinen Klammer- oder Gleichheits-Initialisierer angeben

... was es erlaubt, das oben Genannte weiter zu vereinfachen

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

… Wie von T.C in ein Kommentar zu dieser Antwort vermerkt.

Auch der ​constexpr -Spezifizierer impliziert inline für statische Datenelemente sowie für Funktionen.


Anmerkungen:
¹ Für eine Funktion hat inline auch einen Hinweis auf die Optimierung, dass der Compiler es vorziehen sollte, Aufrufe dieser Funktion durch direkte Ersetzung des Maschinencodes der Funktion zu ersetzen. Dieser Hinweis kann ignoriert werden.

103

Inline-Variablen sind Inline-Funktionen sehr ähnlich. Es signalisiert dem Linker, dass nur eine Instanz der Variablen vorhanden sein sollte, auch wenn die Variable in mehreren Kompilierungseinheiten angezeigt wird. Der Linker muss sicherstellen, dass keine Kopien mehr erstellt werden.

Inline-Variablen können verwendet werden, um Globale nur in Header-Bibliotheken zu definieren. Vor C++ 17 mussten Workarounds (Inline-Funktionen oder Template-Hacks) verwendet werden.

Eine Problemumgehung besteht beispielsweise darin, den Meyer-Singleton mit einer Inline-Funktion zu verwenden:

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

Dieser Ansatz weist einige Nachteile auf, hauptsächlich in Bezug auf die Leistung. Dieser Overhead könnte durch Vorlagenlösungen vermieden werden, aber es ist leicht, sie falsch zu verstehen.

Mit Inline-Variablen können Sie diese direkt deklarieren (ohne dass ein Linker-Fehler mit mehreren Definitionen auftritt):

inline T global;

Abgesehen von Bibliotheken, die nur Header enthalten, gibt es andere Fälle, in denen Inline-Variablen hilfreich sein können. Nir Friedman behandelt dieses Thema in seinem Vortrag auf der CppCon: Was C++ - Entwickler über Globals (und den Linker) wissen sollten . Der Teil über Inline-Variablen und die Problemumgehungen beginnt um 18m9s .

Um es kurz zu machen: Wenn Sie globale Variablen deklarieren müssen, die von den Kompilierungseinheiten gemeinsam genutzt werden, ist es unkompliziert, sie als Inline-Variablen in der Header-Datei zu deklarieren, und die Probleme mit Workarounds vor C++ 17 werden vermieden.

(Es gibt immer noch Anwendungsfälle für den Meyer-Singleton, zum Beispiel, wenn Sie ausdrücklich eine verzögerte Initialisierung wünschen.)

11
Philipp Claßen

Minimales lauffähiges Beispiel

Mit dieser fantastischen C++ 17-Funktion können wir:

  • verwenden Sie bequemerweise nur eine einzige Speicheradresse für jede Konstante
  • speichern Sie es als constexpr: Wie deklariert man constexpr extern?
  • mach es in einer einzigen Zeile aus einer Überschrift

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

Kompilieren und ausführen:

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 .

Siehe auch: Wie funktionieren Inline-Variablen?

C++ - Standard für Inline-Variablen

Der C++ - Standard garantiert, dass die Adressen identisch sind. C++ 17 N4659-Standardentwurf 10.1.6 "Der Inline-Bezeichner":

6 Eine Inline-Funktion oder Variable mit externer Verknüpfung muss in allen Übersetzungseinheiten dieselbe Adresse haben.

cppreference https://en.cppreference.com/w/cpp/language/inline erklärt, dass, wenn static nicht angegeben wird, eine externe Verknüpfung besteht.

Implementierung von GCC-Inline-Variablen

Wir können beobachten, wie es umgesetzt wird mit:

nm main.o notmain.o

was beinhaltet:

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

und man nm sagt über u:

"u" Das Symbol ist ein eindeutiges globales Symbol. Dies ist eine GNU Erweiterung des Standardsatzes von ELF-Symbolbindungen. Für ein solches Symbol stellt der dynamische Linker sicher, dass im gesamten Prozess nur ein Symbol mit diesem Namen und Typ verwendet wird .

wir sehen also, dass es dafür eine spezielle ELF-Erweiterung gibt.

Vor C++ 17: extern const

Vor C++ 17 und in C können wir mit einem extern const Einen sehr ähnlichen Effekt erzielen, der dazu führt, dass ein einzelner Speicherort verwendet wird.

Die Nachteile von inline sind:

  • es ist nicht möglich, die Variable constexpr mit dieser Technik zu erstellen. Nur inline erlaubt Folgendes: Wie deklariert man constexpr extern?
  • es ist weniger elegant, da Sie die Variable in der Header- und der CPP-Datei separat deklarieren und definieren müssen

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 .

Nur Header-Alternativen vor C++ 17

Diese sind nicht so gut wie die extern -Lösung, aber sie funktionieren und belegen nur einen einzigen Speicherort:

Eine constexpr -Funktion, weil constexprinline und inlineerlaubt (erzwingt), dass die Definition auf erscheint jede Übersetzungseinheit :

constexpr int shared_inline_constexpr() { return 42; }

und ich wette, dass jeder anständige Compiler den Aufruf inline wird.

Sie können auch eine statische Ganzzahlvariable const oder constexpr wie folgt verwenden:

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

sie können jedoch nicht die Adresse übernehmen, da sie sonst nicht mehr verwendet wird. Siehe auch: https://en.cppreference.com/w/cpp/language/static "Constant static members "und Definieren von statischen constexpr-Datenelementen

[~ # ~] c [~ # ~]

In C ist die Situation dieselbe wie in C++ vor C++ 17. Ich habe ein Beispiel hochgeladen unter: Was bedeutet "statisch" in C?

Der einzige Unterschied besteht darin, dass in C++ conststatic für globale Variablen impliziert, nicht jedoch in C: C++ - Semantik von `static const` vs` const`

Wie kann man es vollständig einbinden?

TODO: Gibt es eine Möglichkeit, die Variable vollständig inline zu schreiben, ohne Speicherplatz zu belegen?

Ähnlich wie der Präprozessor.

Das würde irgendwie erfordern:

  • verbieten oder Erkennen, ob die Adresse der Variablen verwendet wird
  • fügen Sie diese Informationen zu den ELF-Objektdateien hinzu, und lassen Sie LTO sie optimieren

Verbunden:

Getestet in Ubuntu 18.10, GCC 8.2.0.