Wie funktionieren Inline-Variablen?


124

Auf dem Oulu ISO C ++ Standards Meeting 2016 wurde ein Vorschlag namens Inline Variables vom Normungsausschuss in C ++ 17 abgestimmt.

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


@jotik Ich denke, die äquivalente Operation würde jedes Vorkommen der Variablen durch ihren Wert ersetzen. Normalerweise ist dies nur gültig, wenn die Variable ist const.
Melpomene

5
Das ist nicht das einzige, was das inlineSchlüsselwort für Funktionen tut. inlineWenn das Schlüsselwort auf Funktionen angewendet wird, hat es einen weiteren entscheidenden Effekt, der direkt in Variablen übersetzt wird. Eine inlineFunktion, die vermutlich in einer Header-Datei deklariert ist, führt zum Zeitpunkt der Verknüpfung nicht zu Fehlern beim "Duplizieren von Symbolen", selbst wenn der Header #includevon mehreren Übersetzungseinheiten d wird. inlineWenn das Schlüsselwort auf Variablen angewendet wird, hat es genau das gleiche Ergebnis. Das Ende.
Sam Varshavchik

4
^ Im Sinne von "Ersetzen Sie jeden Aufruf dieser Funktion durch eine inlinedirekte Kopie ihres Codes" ist dies nur eine schwache, unverbindliche Anforderung an den Optimierer. Compiler können angeforderte Funktionen und / oder nicht kommentierte Funktionen nicht inline einbinden. Der eigentliche Zweck des inlineSchlüsselworts besteht vielmehr darin, mehrere Definitionsfehler zu umgehen.
underscore_d

Antworten:


121

Der erste Satz des Vorschlags:

Der inlineSpezifizierer kann sowie auf Funktionen Variablen angewendet werden.

Der garantierte Effekt, der inlineauf eine Funktion angewendet wird, besteht darin, dass die Funktion mit externer Verknüpfung in mehreren Übersetzungseinheiten identisch definiert werden kann. In der Praxis bedeutet dies, dass die Funktion in einem Header definiert wird, der in mehreren Übersetzungseinheiten enthalten sein kann. Der Vorschlag erweitert diese Möglichkeit auf Variablen.

In der Praxis können Sie mit dem (jetzt akzeptierten) Vorschlag das inlineSchlüsselwort verwenden, um eine externe Verknüpfungs- constNamespace-Bereichsvariable oder ein beliebiges staticKlassendatenelement in einer Header-Datei zu definieren, sodass die mehreren Definitionen, die sich ergeben, wenn dieser Header enthalten ist Mit dem Linker sind mehrere Übersetzungseinheiten in Ordnung - es wird nur eine davon ausgewählt.

Bis einschließlich C ++ 14 war die interne Maschinerie dafür vorhanden, um staticVariablen 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 kann man meiner Meinung nach nur noch schreiben

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-Gleich-Initialisierer spezifizieren. Wenn das Mitglied mit dem Bezeichner deklariert ist constexpr, wird es möglicherweise im Namespace-Bereich ohne Initialisierer neu deklariert (diese Verwendung ist veraltet; siehe ‌ DX). In Deklarationen anderer statischer Datenelemente darf kein Klammer-oder-Gleich-Initialisierer angegeben werden

… Damit das oben Genannte weiter vereinfacht werden kann

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

… Wie von TC in einem Kommentar zu dieser Antwort vermerkt .

Der   Bezeichner ​constexprimpliziert auch  inlinestatische Datenelemente sowie Funktionen.


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


2
Außerdem gilt die const-Einschränkung nur für Namespace-Bereichsvariablen. Klassenbereiche (wie Kath::hi) müssen nicht const sein.
TC

4
Neuere Berichte weisen darauf hin, dass die constEinschränkung vollständig aufgehoben wird.
TC

2
@ Nick: Da Richard Smith (der aktuelle C ++ - Ausschuss "Projekteditor") einer der beiden Autoren ist und er "der Codebesitzer des Clang C ++ - Frontends" ist, vermutete Clang. Und das mit clang 3.9.0 bei Godbolt kompilierte Konstrukt . Es warnt davor, dass Inline-Variablen eine C ++ 1z-Erweiterung sind. Ich habe keine Möglichkeit gefunden, die Auswahl und Optionen des Quell- und Compilers zu teilen, daher ist der Link nur auf die Site im Allgemeinen, sorry.
Prost und hth. - Alf

1
Warum wird ein Inline-Schlüsselwort in der Klassen- / Strukturdeklaration benötigt? Warum nicht einfach zulassen static std::string const hi = "Zzzzz...";?
sasha.sochka

2
@EmilianCioca: Nein, Sie würden dem Fiasko der statischen Initialisierungsreihenfolge zuwiderlaufen . Ein Singleton ist im Wesentlichen ein Gerät, um dies zu vermeiden.
Prost und hth. - Alf

15

Inline-Variablen sind Inline-Funktionen sehr ähnlich. Es signalisiert dem Linker, dass nur eine Instanz der Variablen vorhanden sein sollte, selbst 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 Globals nur in Header-Bibliotheken zu definieren. Vor C ++ 17 mussten sie Problemumgehungen (Inline-Funktionen oder Template-Hacks) verwenden.

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 Aufwand könnte durch Vorlagenlösungen vermieden werden, aber es ist leicht, sie falsch zu verstehen.

Mit Inline-Variablen können Sie sie direkt deklarieren (ohne einen Linker-Fehler mit mehreren Definitionen zu erhalten):

inline T global;

Abgesehen von Bibliotheken nur für Header 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 18 Uhr 9 .

Kurz gesagt, wenn Sie globale Variablen deklarieren müssen, die von Kompilierungseinheiten gemeinsam genutzt werden, ist die Deklaration als Inline-Variablen in der Header-Datei unkompliziert und vermeidet die Probleme mit Problemumgehungen vor C ++ 17.

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


11

Minimal 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 deklariere ich constexpr extern?
  • Machen Sie es in einer einzigen Zeile aus einem 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;
}

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 stromaufwärts .

Siehe auch: Wie funktionieren Inline-Variablen?

C ++ - Standard für Inline-Variablen

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

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 staticnicht angegeben, eine externe Verknüpfung besteht.

Implementierung von GCC-Inline-Variablen

Wir können beobachten, wie es implementiert 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 nmsagt ü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 dedizierte ELF-Erweiterung gibt.

Pre-C ++ 17: extern const

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

Die Nachteile inlinesind:

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 stromaufwärts .

Nur Header-Alternativen vor C ++ 17

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

Eine constexprFunktion, weil constexprimpliziertinline und inline erlaubt (erzwingt), dass die Definition auf jeder Übersetzungseinheit erscheint :

constexpr int shared_inline_constexpr() { return 42; }

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

Sie können auch eine constoder eine constexprstatische Ganzzahlvariable 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 oder sie wird odr-verwendet, siehe auch: https://en.cppreference.com/w/cpp/language/static "Konstante statische Elemente " und Definieren statischer Constexpr-Daten Mitglieder

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 ++ für Globale constimpliziert staticwird, in C: C ++ jedoch nicht die Semantik von `static const` vs` const`

Gibt es eine Möglichkeit, es vollständig zu integrieren?

TODO: Gibt es eine Möglichkeit, die Variable vollständig zu inline, ohne überhaupt Speicher zu verwenden?

Ähnlich wie der Präprozessor.

Dies 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 sie von LTO optimieren

Verbunden:

Getestet in Ubuntu 18.10, GCC 8.2.0.


2
inlinehat fast nichts mit Inlining zu tun, weder für Funktionen noch für Variablen, trotz des Wortes selbst. inlineweist den Compiler nicht an, etwas zu inline. Es weist den Linker an, sicherzustellen, dass es nur eine Definition gibt, die traditionell die Aufgabe des Programmierers war. Also "Gibt es eine Möglichkeit, es vollständig zu integrieren?" ist zumindest eine völlig unabhängige Frage.
Nicht-Benutzer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.