Inline-Funktionen in C ++. Was ist der Punkt?

19

Nach meinem Verständnis ist der Compiler nicht verpflichtet, den Funktionsaufruf einer Inline-Funktion durch seinen Body zu ersetzen, sondern wird dies tun, wenn dies möglich ist. Das brachte mich zum Nachdenken - warum haben wir das Inline-Wort, wenn das der Fall ist? Warum nicht alle Funktionen standardmäßig inline machen und den Compiler herausfinden lassen, ob er die Aufrufe durch den Funktionshauptteil ersetzen kann oder nicht?

EpsilonVector
quelle
1
Hier . Seite 6
EpsilonVector

Antworten:

40

inlineist von C; es war nicht neu in C ++.

Es gibt C-Schlüsselwörter ( registerund inline), mit denen der Programmierer die Codeoptimierung unterstützen kann. Diese werden heutzutage im Allgemeinen ignoriert, da Compiler die Registerzuweisung und die Entscheidung über Inline-Funktionen verbessern können (tatsächlich kann ein Compiler eine Funktion zu verschiedenen Zeiten inline oder nicht inline setzen). Die Codegenerierung auf modernen Prozessoren ist weitaus komplizierter als bei den deterministischeren Prozessoren, die bei Ritchie als Erfinder von C üblich waren.

In C ++ bedeutet das Wort jetzt, dass es mehrere identische Definitionen haben kann und in jeder Übersetzungseinheit definiert werden muss, die es verwendet. (Mit anderen Worten, Sie müssen sicherstellen, dass es eingebettet werden kann.) Sie können inlineproblemlos eine Funktion in einer Kopfzeile haben, und die in einer Klassendefinition definierten Elementfunktionen werden automatisch wirksam inline.

David Thornley
quelle
3
+1 für die Geschichte von inline.
Tamara Wijsman
11
Ich bin mir ziemlich sicher, dass dies zuerst inlinein C ++ standardisiert war, obwohl es bereits als Herstellererweiterung in C verfügbar war.
Ben Voigt
@Ben Voigt: Du hast recht. Ich habe es zum ersten Mal in C.
David Thornley am
5
Beachten Sie auch, dass nicht nur der Verlauf falsch ist, sondern die Regeln für inlineC99 und höher sich von den Regeln für inlineC ++ unterscheiden.
Alf P. Steinbach
27

Ursprünglich inlinewar ein sehr starker Hinweis, dass Aufrufe der Funktion inline sein sollten.

Der einzige garantierte Effekt von inlineist jedoch, dass eine Funktion in mehreren Übersetzungseinheiten (effektiv identisch) definiert werden kann, z. B., dass Sie die Definition in eine Header-Datei einfügen.

Heutzutage sind einige Compiler sehr daran interessiert, dem Inlining-Hinweis zu folgen, z. B. g ++. Und einige Compiler nehmen es weniger ernst, z. B. Visual C ++. Aber alle müssen sich an die Garantie halten.

Es ist bedauerlich, dass diese beiden Bedeutungen - Optimierungshinweis und was wir als verwerfbare Definition auf Linker-Ebene bezeichnen - mit demselben Schlüsselwort verknüpft sind, da dies bedeutet, dass Sie praktisch keine ohne die andere haben können.

Es ist auch bedauerlich, dass inline(oder besser, ein separates Schlüsselwort zur verwerfbaren Definition) ¹nicht auf Daten angewendet werden kann .

Der Bedarf an verwerfbaren Daten auf Linker-Ebene hat zugenommen, da reine Header-Module immer beliebter werden. Beispielsweise sind viele Boost-Unterbibliotheken nur für den Header bestimmt.

Für Daten können Sie jedoch einen kleinen Trick mit Vorlagen anwenden. Definieren Sie es in einer Klassenvorlage, geben Sie einen typedefVorlagenparameter an void(oder was auch immer). Das liegt daran, dass die One Definition Rule eine bestimmte Ausnahme für Vorlagen macht.

Anmerkungen:
¹ inlineVariablen werden in C ++ 17 unterstützt .

Alf P. Steinbach
quelle
1
+1 für die zusätzlichen Informationen über inline.
Tamara Wijsman
1
" effektiv identisch " ist eigentlich eine sehr schwierige Bedingung, da die sehr schlecht gestalteten C ++ - Sprachen sich sehr bemühen, normalerweise kompetente und sorgfältige Programmierer dazu zu bringen, vernünftigen, aber formal undefinierten Code zu schreiben, der statische Objekte verwendet - ich frage mich, wie viele Ausschussmitglieder selbst in die Falle tappen
neugierig
6

Warum nicht alle Funktionen standardmäßig inline machen? Weil es ein technischer Kompromiss ist. Es gibt mindestens zwei Arten der "Optimierung": Beschleunigen des Programms und Verringern der Größe (Speicherbedarf) des Programms. Inlining beschleunigt die Dinge im Allgemeinen. Der Funktionsaufruf-Overhead entfällt, und es wird vermieden, Parameter vom Stapel zu ziehen. Dies vergrößert jedoch auch den Speicherbedarf des Programms, da jeder Funktionsaufruf jetzt durch den vollständigen Code der Funktion ersetzt werden muss. Denken Sie daran, dass die CPU häufig verwendete Speicherbereiche in einem Cache auf der CPU speichert, um einen ultraschnellen Zugriff zu ermöglichen. Wenn Sie das Speicherimage des Programms groß genug machen, kann Ihr Programm den Cache nicht effizient nutzen, und im schlimmsten Fall kann das Inlining Ihr Programm tatsächlich verlangsamen.

Charles E. Grant
quelle
5
Aber der Compiler kann (und tut!) Dies viel besser als der Programmierer im Allgemeinen. Das ist also kein gültiges Argument.
Konrad Rudolph
" Jeder Funktionsaufruf muss jetzt durch den vollständigen Code der Funktion ersetzt werden. " Und die Inline-Funktion ist keine Funktion, bei der die Aufrufsequenz weggelassen und der Assembler-Code der Funktion kopiert wird. Eine Inline-Funktion wird kompiliert, indem der Zwischencode auf hoher Ebene inline geschaltet wird. Auf diese Weise kann der Compiler den Funktionskörper effektiv als sauberes Makro behandeln (ein sauberes Makro weist nicht die Macken von C-Präprozessor-Makros auf). Möglicherweise sind viele Optimierungen verfügbar.
neugierig
3

Um „Inline“ zu verstehen, muss man die Geschichte verstehen und wissen, wie das Leben vor 20 (und 30) Jahren war.

Wir haben Code auf Computern mit wenig Arbeitsspeicher geschrieben, daher war es einem Compiler nicht möglich, den gesamten Code, aus dem ein Programm bestand, auf einmal zu verarbeiten. Der Compiler war auch sehr langsam, sodass Sie keinen Code neu kompilieren mussten, der sich nicht geändert hatte - 24 Stunden (auf einem Computer, der mehr als ein Top-End-Auto kostete), um den gesamten Code neu zu kompilieren, war für einige Projekte, die ich durchgeführt habe, normal arbeitete an.

Daher wurde jede Codedatei separat in eine Objektdatei kompiliert. Jede Objektdatei begann mit einer Liste aller darin enthaltenen Funktionen, zusammen mit der „Adresse“ der Funktion. Eine Objektdatei enthielt auch eine Liste aller aufgerufenen Funktionen in anderen Objektdateien sowie den Speicherort des Aufrufs.

Ein Linker würde zuerst alle Objektdateien lesen und eine Liste aller von ihm exportierten Funktionen zusammen mit der Datei, in der sie sich befanden, und ihrer Adresse erstellen. Es würde dann alle Objektdateien erneut lesen und sie in die Programmdatei ausgeben, während alle "externen" Funktionsaufrufe mit der Adresse der Funktion aktualisiert würden.

Der Linker hat den vom Compiler erstellten Maschinencode in keiner anderen Weise geändert oder optimiert, als um Verweise auf externe Funktionsaufrufe zu korrigieren. Der Linker war Teil des Betriebssystems und älter als die meisten Compiler. Wenn Leute einen neuen Compiler schrieben, brauchten sie ihn, um mit aktuellen Linkern zu arbeiten und um in der Lage zu sein, auf aktuelle Objektdateien zu verlinken, sonst könnten keine Systemaufrufe gemacht werden.

Der Compiler hat den Code immer nur in der „.c“ - oder „.cpp“ -Datei gesehen, die er zusammen mit allen enthaltenen Header-Dateien kompilierte. Daher konnte keine Optimierung basierend auf Code in anderen „.c“ - oder „.cpp“ -Dateien durchgeführt werden.

Mit dem Schlüsselwort "inline" konnte der Hauptteil einer Funktion (Methode) in einer Header-Datei definiert werden, sodass der Compiler den Code der Funktion beim Kompilieren des aufrufenden Codes verwenden konnte. Angenommen, Sie hätten eine Auflistungsklasse in einer anderen .cpp-Datei definiert. Diese Klasse hätte eine "isEmpty" -Methode, die eine Codezeile enthielte. Anstelle eines Funktionsaufrufs würde das resultierende Programm erheblich beschleunigt wurde der Funktionsaufruf durch diese eine Zeile ersetzt.

Das Schlüsselwort "Inline" wurde zu der Zeit als "billig und einfach" angesehen, um die Kapselung von Daten zu ermöglichen und gleichzeitig die Kosten für Funktionsaufrufe zu vermeiden. Ohne dieses Schlüsselwort hätten viele Programmierer nur Zugriff auf die privaten Felder des Objekts. (Makros, bei denen ein viel schlechterer Weg war, Code zu „inlinen“, als dies zu der Zeit üblich war.)

Heutzutage führen „Linker“ eine Menge Code-Optimierung durch und werden in der Regel von einem Team als Compiler geschrieben. Der Compiler überprüft oft nur, ob der Code korrekt ist, und „komprimiert“ ihn, sodass der Linker die meiste Aufgabe der Maschinencodeerstellung übernimmt.

Ian
quelle
2

Mal sehen, was der Standard sagt (hervorgehobene wichtige Teile in Fettdruck):

2. Eine Funktionsdeklaration mit einem Inline-Bezeichner deklariert eine Inline-Funktion. Der Inline-Bezeichner gibt der Implementierung an, dass die Inline-Ersetzung des Funktionskörpers am Aufrufpunkt dem üblichen Funktionsaufrufmechanismus vorzuziehen ist. Eine Implementierung ist nicht erforderlich, um diese Inline-Substitution zum Zeitpunkt des Aufrufs durchzuführen . Selbst wenn diese Inline-Ersetzung weggelassen wird, müssen die anderen Regeln für Inline-Funktionen dennoch eingehalten werden.

- C ++ - Standard, ISO / IEC 14882: 2003 , 7.1.2 Funktionsspezifizierer [dcl.fct.spec]

Wenn Sie also sicher sein möchten, sollten Sie die Dokumentation Ihres Compilers lesen.

Alles einzufügen ist eine schlechte Idee, da es zu einer Menge dupliziertem Maschinencode führen kann ...

Also musst du wissen:

Es gibt keine einfachen Antworten: Sie müssen damit spielen, um zu sehen, was das Beste ist. Geben Sie sich nicht mit simplen Antworten wie "Niemals inlineFunktionen verwenden" oder "Immer inlineFunktionen verwenden" oder " inlineFunktionen nur dann verwenden, wenn die Funktion weniger als N Codezeilen enthält" zufrieden. Diese Einheitsregeln lassen sich zwar leicht aufschreiben, führen jedoch zu nicht optimalen Ergebnissen.

- C ++ - FAQ, Inline-Funktionen , 9.3 inlineVerbessern Funktionen die Leistung?

Tamara Wijsman
quelle
1
Was Sie sagen, ist wahr, aber nicht relevant, weil es als Programmierer nicht Ihre Entscheidung ist, ob Sie inline arbeiten oder nicht. Wenn Sie das Schlüsselwort eingeben, werden Ihre Funktionen möglicherweise eingebunden oder nicht. Wenn Sie das Schlüsselwort nicht eingeben, werden sie möglicherweise immer noch eingebunden oder nicht eingebunden. Es ist sinnlos, irgendwelche Regeln zu haben, wenn Sie das Schlüsselwort setzen, was mit dem Compiler derjenige ist, der tatsächlich die Entscheidung trifft.
Kate Gregory
Wie ist das nicht relevant, wenn es genauso aussagt wie Sie? Es gibt keine einfachen Antworten .
Tamara Wijsman
Es ist nicht relevant, weil es die Frage des OP nicht beantwortet. Er fragt nicht "Was macht Inline?", Sondern "Worum geht es?". Und Ihre Antwort wiederholt nur, was wir alle bereits über Inline wissen.
Davor Ždralo
@Davor: "Was ist der Sinn?" hält sich nicht an die FAQ, daher beantworte ich die in der Frage gestellten Fragen. Ich sage den Punkt, inlineindem ich das Wissen wiederhole, da dem OP ein Teil zu fehlen scheint, was der Hauptgrund ist, warum er den Punkt nicht versteht. Um einen Punkt zu bekommen, muss er verstehen, was unter diesem Punkt liegt ...
Tamara Wijsman
Warum zum Teufel würde es nicht an den FAQ festhalten? Standard beschreibt die Syntax und Semantik von Inline-Schlüsselwörtern, und die Frage von OP ist, was der Zweck ist, wann es verwendet werden sollte, welche Probleme es lösen soll. Ich verstehe nicht, wie das den FAQ nicht entspricht.
Davor Ždralo
1

Lassen Sie mich einen guten Grund für die Verwendung des Inline-Schlüsselworts nennen.

Auf einem eingebetteten System, z. B. einem Ticketdrucker oder einem ähnlichen kleineren System. Der Prozessor ist sehr begrenzt und ein Funktionsaufruf (Vorbereiten von Funktionsparametern auf dem Stack, Aufrufen, Abrufen von Parametern vom Stack und Zurücksetzen der Antwort usw.) kann neben der eigentlichen Funktion mehrere ms dauern, bis er ausgeführt wird.

Nehmen wir an, die Anrufzeit beträgt ca. 60 ms (nur zum Aufrufen, nicht für die eigentliche Funktion) und Sie haben 50 Iterationen (Schleifen- oder iterative Aufrufe in einem Baum).

Die Zeit, um von diesem Funktionsaufruf einfach vor und zurück zu gehen, beträgt 60 * 50 = 3000 (3 Sekunden).

Wenn Sie den Speicher haben, würden Sie definitiv eine Inline-Aktion ausführen, um 3 Sekunden zu sparen.

Inline werden also im Grunde genommen verwendet, wenn Sie Ausführungsgeschwindigkeit benötigen. In einigen Projekten, an denen ich beteiligt war, war die Aufrufzeit länger als die Ausführungszeit, eine klassische Situation bei der Verwendung von Inline.

Max Kielland
quelle
Äh, was? Das Einfügen in den Code bedeutet nicht, dass der Compiler ihn tatsächlich einfügt, und das Nichteinfügen in den Code bedeutet auch nicht, dass dies nicht der Fall ist. Es ist nur ein Hinweis, der von den heutigen Compilern meist ignoriert wird. Wenn es der Compiler für nützlich hält, inline zu schreiben, wird es anders nicht funktionieren. Sie haben dort keine wirkliche Kontrolle. OP weiß dies bereits und fragt, ich zitiere: "Was ist der Sinn?". Wer zum Teufel gibt +1 dazu? Dies ist sowohl irreführend (was impliziert, dass das Keyword Inline Inlining erzwingt) als auch für die Frage irrelevant.
Davor Ždralo
2
@Davor Ždralo Du musst nicht unhöflich sein. Ich interpretierte die Frage als WARUM sollte ich Inlines verwenden? Mein Ziel war es zu zeigen, dass Sie den Code auf Kosten des Arbeitsspeichers schneller erhalten können. Jeder Compiler kann das Inline-Schlüsselwort anders behandeln, daher müssen Sie die Dokumentation überprüfen, die ich nicht erwähnt habe. Da Sie sich den zusätzlichen Speicheraufwand, den Inlines verursachen, nicht immer leisten können, ist es hilfreich, den Verwendungszeitpunkt zu "leiten". Ich habe auch nicht gesagt, dass Inlines erzwungen werden. Jemand fand diese Antwort nützlich für ihn / sie und stimmte für +1. Bitte respektieren Sie, dass andere möglicherweise eine andere Erfahrungsebene haben und etwas Triviales nützlich finden.
Max Kielland