Wie funktionieren Inline-Variablen?

Lesezeit: 11 Minuten

Wie funktionieren Inline Variablen
Jotik

Beim ISO C++ Standards Meeting 2016 in Oulu wurde ein Vorschlag genannt Inline-Variablen wurde vom Standards Committee in C++17 gewählt.

Laienhaft ausgedrückt, was sind Inline-Variablen, wie funktionieren sie und wofür sind sie nützlich? Wie sollten 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.

    – Melpomen

    26. Juni 2016 um 22:17 Uhr

  • Das ist nicht das einzige, was die inline Schlüsselwort does für Funktionen. Die inline Wenn es auf Funktionen angewendet wird, hat das Schlüsselwort einen weiteren entscheidenden Effekt, der sich direkt auf Variablen überträgt. Ein inline -Funktion, die vermutlich in einer Header-Datei deklariert ist, führt zur Verbindungszeit nicht zu Fehlern durch doppelte Symbole, selbst wenn der Header abgerufen wird #included durch mehrere Übersetzungseinheiten. Die inline Das Schlüsselwort hat, wenn es auf Variablen angewendet wird, genau das gleiche Ergebnis. Das Ende.

    – Sam Varshavchik

    26. Juni 2016 um 22:22 Uhr


  • ^ Im Sinne von “jeden Aufruf dieser Funktion durch eine direkte Kopie ihres Codes ersetzen”, inline ist nur eine schwache, unverbindliche Aufforderung an den Optimierer. Compilern steht es frei, angeforderte Funktionen nicht einzufügen und/oder diejenigen einzufügen, die Sie nicht kommentiert haben. Vielmehr ist der eigentliche Zweck der inline Schlüsselwort ist, mehrere Definitionsfehler zu umgehen.

    – Unterstrich_d

    25. Juli 2016 um 7:46 Uhr


Wie funktionieren Inline Variablen
Prost und hth. – Alf

Der erste Satz des Vorschlags:

Dieinline specifier kann sowohl auf Variablen als auch auf Funktionen angewendet werden.

Die ¹garantierte Wirkung von inline wie es auf eine Funktion angewendet wird, soll ermöglichen, dass die Funktion mit externer Verknüpfung in mehreren Übersetzungseinheiten identisch definiert wird. Für die Praxis bedeutet das, 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 erlaubt Ihnen der (jetzt akzeptierte) Vorschlag also, die zu verwenden inline Schlüsselwort, um eine externe Verknüpfung zu definieren const Namespace-Bereichsvariable oder eine beliebige static Klassendatenelement 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 – er wählt einfach aus ein von ihnen.

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

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 angeben. Wenn das Mitglied mit deklariert ist constexpr Spezifizierer, kann er im Namespace-Bereich ohne Initialisierer neu deklariert werden (diese Verwendung ist veraltet; siehe‌​ DX). Deklarationen anderer statischer Datenmember dürfen keinen Klammer-oder-Gleichheits-Initialisierer angeben

… wodurch das Obige weiter vereinfacht werden kann

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

… wie von TC in einem Kommentar zu dieser Antwort angemerkt.

Auch der ​constexprBezeichner impliziert inline für statische Datenmember sowie Funktionen.


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

  • Außerdem gilt die const-Einschränkung nur für Namespace-Gültigkeitsvariablen. Klassenbezogene (wie Kath::hi) müssen nicht konstant sein.

    – TC

    26. Juni 2016 um 22:05 Uhr

  • Neuere Berichte weisen darauf hin, dass die const Die Beschränkung entfällt vollständig.

    – TC

    6. Juli 2016 um 19:19 Uhr

  • @Nick: Da Richard Smith (das aktuelle C++-Komitee „Projekteditor“) einer der beiden Autoren ist und da er „der Code-Besitzer des Clang-C++-Frontends“ ist, vermutete Clang. Und das Konstrukt kompiliert mit clang 3.9.0 drüben bei Gottbolzen. Es warnt davor, dass Inline-Variablen eine C++1z-Erweiterung sind. Ich habe keine Möglichkeit gefunden, die Quell- und Compiler-Auswahl und -Optionen zu teilen, also führt der Link nur zur Website im Allgemeinen, sorry.

    – Prost und hth. – Alf

    28. September 2016 um 17:31 Uhr


  • Warum ist ein Inline-Schlüsselwort innerhalb der Klassen-/Strukturdeklaration erforderlich? Warum nicht einfach zulassen static std::string const hi = "Zzzzz...";?

    – sasha.sochka

    19. Mai 2017 um 1:33 Uhr

  • @EmilianCioca: Nein, du würdest damit in Konflikt geraten Fiasko der statischen Initialisierungsreihenfolge. Ein Singleton ist im Wesentlichen ein Gerät, um dies zu vermeiden.

    – Prost und hth. – Alf

    1. August 2017 um 23:39 Uhr

1647112812 248 Wie funktionieren Inline Variablen
Philipp Klaßen

Inline-Variablen sind Inline-Funktionen sehr ähnlich. Es signalisiert dem Linker, dass nur eine Instanz der Variablen existieren sollte, selbst wenn die Variable in mehreren Kompilierungseinheiten gesehen wird. Der Linker muss sicherstellen, dass keine weiteren Kopien erstellt werden.

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

Eine Problemumgehung besteht beispielsweise darin, die Meyers Singleton mit einer Inline-Funktion:

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

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

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

inline T global;

Abgesehen von reinen Header-Bibliotheken 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 bei 18m9s.

Um es kurz zu machen, wenn Sie globale Variablen deklarieren müssen, die von Kompilierungseinheiten gemeinsam genutzt werden, ist das Deklarieren 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 Meyers Singleton, beispielsweise wenn Sie explizit eine verzögerte Initialisierung wünschen.)

  • Können Sie mehr auf Leistungsprobleme mit Meyers Singleton eingehen?

    – Juraj Oršulić

    14. August 2020 um 19:59 Uhr

  • @JurajOršulić Für die meisten Anwendungsfälle ist der Overhead des Meyer-Singletons-Musters nicht hoch. Compiler optimieren stark für den schnellen Pfad (dh Zugriff auf eine bereits initialisierte Variable). Dennoch gibt es Overhead, da Sie Thread-sicheren Code bereitstellen müssen, um die verzögerte Initialisierung zu handhaben. Für Details wird das Sprachfeature manchmal als „magische Statik“ bezeichnet und wurde in C++11 eingeführt. Das N2660-Papier enthält Informationen darüber, wie Compiler es effizient implementieren können, um den Overhead auf ein Minimum zu reduzieren: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm#Appendix

    – Philipp Klaßen

    14. August 2020 um 22:16 Uhr

  • Ah, Sie haben also über den Aufwand gesprochen, die Initialisierung der magischen Statik Thread-sicher zu machen, anstatt Ihre eigene nicht-Thread-sichere Version mit Vorlagen zu erstellen (wie würden Sie das anstellen, wenn Sie diese neu eingeführten Inline nicht verwenden würden global?). Danke für die Ausarbeitung.

    – Juraj Oršulić

    15. August 2020 um 21:22 Uhr


1647112813 915 Wie funktionieren Inline Variablen
Ciro Santilli Путлер Капут 六四事

Minimales lauffähiges Beispiel

Mit dieser großartigen C++17-Funktion können wir:

  • Verwenden Sie praktischerweise nur eine einzige Speicheradresse für jede Konstante
  • speichern Sie es als constexpr: Wie deklariert man constexpr extern?
  • tun Sie es in einer einzigen Zeile aus einer Kopfzeile

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

cpReferenz https://en.cppreference.com/w/cpp/language/inline erklärt das wenn static nicht gegeben ist, dann hat es eine externe Verlinkung.

GCC-Inline-Variablenimplementierung

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 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 dedizierte ELF-Erweiterung gibt.

Vor C++ 17: extern const

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

Die Nachteile vorbei inline sind:

  • es ist nicht möglich, die Variable zu machen constexpr nur mit dieser Technik inline erlaubt das: Wie deklariert man constexpr extern?
  • es ist weniger elegant, da Sie die Variable in der Header- und 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 Speicherplatz:

EIN constexpr funktionieren, weil constexpr impliziert inline 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 einbetten wird.

Sie können auch eine verwenden const oder constexpr statische Integer-Variable wie 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;
}

aber Sie können nicht seine Adresse nehmen, sonst wird es odr-benutzt, siehe auch: https://en.cppreference.com/w/cpp/language/static “Konstante statische Elemente” und Statische Datenelemente von constexpr definieren

C

In C ist die Situation die gleiche wie in C++ vor C++ 17, ich habe ein Beispiel hochgeladen unter: What does “static” mean in C?

Der einzige Unterschied besteht darin, dass in C++ const impliziert static für Globals, aber nicht in C: C++-Semantik von `static const` vs `const`

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

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

Ähnlich wie das, was der Präprozessor tut.

Dies würde irgendwie erfordern:

  • Verbieten oder Erkennen, ob die Adresse der Variablen belegt ist
  • Fügen Sie diese Informationen zu den ELF-Objektdateien hinzu und lassen Sie sie von LTO optimieren

Verwandt:

  • C++11-Enumeration mit Klassenmitgliedern und constexpr-Linkzeitoptimierung

Getestet in Ubuntu 18.10, GCC 8.2.0.

  • inline hat fast nichts mit Inlining zu tun, weder für Funktionen noch für Variablen, trotz des Wortes selbst. inline weist den Compiler nicht an, irgendetwas einzufügen. Es weist den Linker an, sicherzustellen, dass es nur eine Definition gibt, was traditionell die Aufgabe des Programmierers war. Also “Irgendeine Möglichkeit, es vollständig zu inlinen?” ist zumindest eine völlig unabhängige Frage.

    – kein Benutzer

    27. Juni 2019 um 21:23 Uhr

  • @ not-as-user Es ist ein verbreiteter Mythos, dass Inline nichts mit Inlining zu tun hat. Visual Studio verwendet es als Hinweis und wird ansonsten nicht eingebunden (außer /LTCG). Umgekehrt gilt nicht, dass es bei Inline immer Inlines gibt. Tatsächlich wird es manchmal trotz des Schlüsselworts in supereinfachen One-Line-Gettern nicht inline eingebunden.

    – Gast128

    8. Januar um 10:49 Uhr

  • Ich könnte meinen ganzen Repräsentanten dafür ausgeben, Antworten wie diese +50 zu belohnen 🙂

    – Matthias

    9. März um 10:50 Uhr

  • @Matthieu danke für die Unterstützung! Hüten Sie sich jedoch auf jeden Fall vor den Auswirkungen von Wahlbetrug, ich denke, Sie sind sich dessen bewusst. Selbst Kopfgeld für eine einzelne Frage mit einem vordefinierten Benutzer im Hinterkopf ist ein potenzielles Risiko. Trotzdem schätze ich die Idee, seinen gesamten Ruf zu verschenken, Sand-Mandalas kommen mir in den Sinn: en.wikipedia.org/wiki/Sand_mandala Es wäre cool, wenn es eine Abfrage geben würde, um den Benutzer zu finden, der jemals die meisten Kopfgelder vergeben hat.

    – Ciro Santilli Путлер Капут 六四事

    9. März um 11:04 Uhr

994980cookie-checkWie funktionieren Inline-Variablen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy