Ist "inline" ohne "statisch" oder "extern" in C99 jemals nützlich?

95

Wenn ich versuche, diesen Code zu erstellen

inline void f() {}

int main()
{
    f();
}

über die Kommandozeile

gcc -std=c99 -o a a.c

Ich erhalte einen Linkerfehler (undefinierter Verweis auf f). Der Fehler verschwindet, wenn ich static inlineoder extern inlinestatt nur verwende inlineoder wenn ich mit kompiliere -O(die Funktion ist also tatsächlich inline).

Dieses Verhalten scheint in Absatz 6.7.4 (6) des C99-Standards definiert zu sein:

Wenn alle Dateibereichsdeklarationen für eine Funktion in einer Übersetzungseinheit den inlineFunktionsspezifizierer ohne enthalten extern, ist die Definition in dieser Übersetzungseinheit eine Inline-Definition. Eine Inline-Definition bietet keine externe Definition für die Funktion und verbietet keine externe Definition in einer anderen Übersetzungseinheit. Eine Inline-Definition bietet eine Alternative zu einer externen Definition, mit der ein Übersetzer jeden Aufruf der Funktion in derselben Übersetzungseinheit implementieren kann. Es ist nicht angegeben, ob ein Aufruf der Funktion die Inline-Definition oder die externe Definition verwendet.

Wenn ich das alles richtig verstehe, kompiliert eine Kompilierungseinheit mit einer inlinewie im obigen Beispiel definierten Funktion nur dann konsistent, wenn es auch eine externe Funktion mit demselben Namen gibt, und ich weiß nie, ob meine eigene Funktion oder die externe Funktion aufgerufen wird.

Ist dieses Verhalten nicht völlig dumm? Ist es jemals sinnvoll, eine Funktion inlineohne staticoder externin C99 zu definieren ? Vermisse ich etwas

Zusammenfassung der Antworten

Natürlich hat mir etwas gefehlt und das Verhalten ist nicht dumm. :) :)

Wie Nemo erklärt , besteht die Idee darin, die Definition der Funktion festzulegen

inline void f() {}

in der Header-Datei und nur eine Deklaration

extern inline void f();

in der entsprechenden .c-Datei. Nur die externDeklaration löst die Erzeugung von extern sichtbarem Binärcode aus. Und es gibt in der Tat keine Verwendung inlinein einer .c-Datei - es ist nur in Headern nützlich.

Wie die in Jonathans Antwort zitierte Begründung des C99-Komitees erläutert, inlinedreht sich alles um Compiler-Optimierungen, bei denen die Definition einer Funktion am Ort eines Aufrufs sichtbar sein muss. Dies kann nur erreicht werden, indem die Definition in den Header eingefügt wird, und natürlich darf eine Definition in einem Header nicht jedes Mal Code ausgeben, wenn sie vom Compiler gesehen wird. Da der Compiler jedoch nicht gezwungen ist, eine Funktion tatsächlich zu inline, muss irgendwo eine externe Definition vorhanden sein.

Sven Marnach
quelle
Borderline-Duplikat von stackoverflow.com/questions/5369888/…
Earlz
@Earlz: Danke für den Link. Meine Frage bezieht sich auf die Gründe für den zitierten Absatz des Standards und darauf, ob es jemals Anwendungsfälle für inlineohne staticund externgibt. Leider wird keines dieser Probleme in dieser Frage behandelt.
Sven Marnach
@Nemo: Ich habe diese Frage und die Antworten gelesen, bevor ich meine gepostet habe. Wieder frage ich nicht "wie verhält es sich?" sondern "Was ist die Idee hinter diesem Verhalten"? Ich bin mir ziemlich sicher, dass mir hier etwas fehlt.
Sven Marnach
1
Ja, deshalb habe ich "Borderline" gesagt. Ich persönlich denke, das stellt eine ganz andere Frage
Earlz

Antworten:

40

Eigentlich beantwortet diese hervorragende Antwort auch Ihre Frage, denke ich:

Was macht extern inline?

Die Idee ist, dass "inline" in einer Header-Datei und dann "extern inline" in einer .c-Datei verwendet werden kann. Mit "extern inline" weisen Sie den Compiler an, welche Objektdatei den (extern sichtbaren) generierten Code enthalten soll.

[aktualisieren, ausarbeiten]

Ich glaube nicht, dass "inline" (ohne "static" oder "extern") in einer .c-Datei verwendet werden kann. In einer Header-Datei ist dies jedoch sinnvoll und erfordert eine entsprechende "externe Inline" -Deklaration in einer .c-Datei, um den eigenständigen Code tatsächlich zu generieren.

Nemo
quelle
1
Danke, ich fange an, auf die Idee zu kommen!
Sven Marnach
8
Ja, ich habe das bis jetzt selbst nie verstanden, also danke, dass du gefragt hast :-). Es ist insofern seltsam, als die nicht externe "Inline" -Definition in den Header eingefügt wird (aber nicht unbedingt zu einer Codegenerierung führt), während die "externe Inline" -Deklaration in die .c-Datei eingefügt wird und den Code tatsächlich dazu veranlasst generiert werden.
Nemo
3
Bedeutet das, dass ich inline void f() {}in den Header und extern inline void f();in die .c-Datei schreibe ? Die eigentliche Funktionsdefinition geht also in den Header und die .c-Datei enthält in diesem Fall lediglich eine Deklaration in umgekehrter Reihenfolge?
Sven Marnach
1
@endolith: Ich verstehe deine Frage nicht. Wir diskutieren inline ohne static oder extern. Natürlich static inlineist das in Ordnung, aber darum geht es in dieser Frage und Antwort nicht.
Nemo
2
@MatthieuMoy Sie müssen -std=c99anstelle von verwenden -std=gnu89.
A3 vom
27

Aus der Norm (ISO / IEC 9899: 1999) selbst:

Anhang J.2 Undefiniertes Verhalten

  • ...
  • Eine Funktion mit externer Verknüpfung wird mit einem deklariert inline Funktionsspezifizierer , aber nicht in derselben Übersetzungseinheit (6.7.4) definiert.
  • ...

Das C99-Komitee hat eine Begründung verfasst , in der es heißt:

6.7.4 Funktionsspezifizierer

Eine neue Funktion von C99: Das inlineaus C ++ adaptierte Schlüsselwort ist ein Funktionsspezifizierer , der nur in Funktionsdeklarationen verwendet werden kann. Dies ist nützlich für Programmoptimierungen, bei denen die Definition einer Funktion am Ort eines Aufrufs sichtbar sein muss. (Beachten Sie, dass der Standard nicht versucht, die Art dieser Optimierungen anzugeben.)

Die Sichtbarkeit ist gewährleistet, wenn die Funktion über eine interne Verknüpfung verfügt oder wenn sie über eine externe Verknüpfung verfügt und sich der Aufruf in derselben Übersetzungseinheit wie die externe Definition befindet. In diesen Fällen hat das Vorhandensein des inlineSchlüsselworts in einer Deklaration oder Definition der Funktion keine Auswirkung, außer dass eine Präferenz angegeben wird, dass Aufrufe dieser Funktion gegenüber Aufrufen anderer Funktionen, die ohne die deklariert wurden, bevorzugt optimiert werden sollteninline Schlüsselwort .

Die Sichtbarkeit ist ein Problem für einen Aufruf einer Funktion mit externer Verknüpfung, bei der sich der Aufruf in einer anderen Übersetzungseinheit als der Funktionsdefinition befindet. In diesem Fall ist dieinline ermöglicht Schlüsselwort, dass die Übersetzungseinheit, die den Aufruf enthält, auch eine lokale oder Inline-Definition der Funktion enthält.

Ein Programm kann eine Übersetzungseinheit mit einer externen Definition, eine Übersetzungseinheit mit einer Inline-Definition und eine Übersetzungseinheit mit einer Deklaration, jedoch ohne Definition für eine Funktion enthalten. Anrufe in der letzteren Übersetzungseinheit verwenden wie gewohnt die externe Definition.

Eine Inline-Definition einer Funktion wird als eine andere Definition als die externe Definition angesehen. Wenn ein Aufruf einer Funktion funcmit externer Verknüpfung erfolgt, bei der eine Inline-Definition sichtbar ist, ist das Verhalten dasselbe, als würde beispielsweise eine andere Funktion aufgerufen __func mit interner Verknüpfung, erfolgt wäre. Ein konformes Programm darf nicht davon abhängen, welche Funktion aufgerufen wird. Dies ist das Inline-Modell im Standard.

Ein konformes Programm darf sich weder auf die Implementierung unter Verwendung der Inline-Definition noch auf die Implementierung unter Verwendung der externen Definition verlassen. Die Adresse einer Funktion ist immer die Adresse, die der externen Definition entspricht. Wenn diese Adresse jedoch zum Aufrufen der Funktion verwendet wird, kann die Inline-Definition verwendet werden. Daher verhält sich das folgende Beispiel möglicherweise nicht wie erwartet.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Da die Implementierung möglicherweise die Inline-Definition für einen der Aufrufe saddrund die externe Definition für den anderen verwendet, kann nicht garantiert werden, dass die Gleichheitsoperation 1 (true) ergibt. Dies zeigt, dass sich statische Objekte, die in der Inline-Definition definiert sind, von ihrem entsprechenden Objekt in der externen Definition unterscheiden. Dies motivierte die Einschränkung, nicht einmal ein Nicht- zu definierenconst dieses Typs zu definieren.

Inlining wurde dem Standard so hinzugefügt, dass es mit der vorhandenen Linker-Technologie implementiert werden kann und eine Teilmenge von C99-Inlining mit C ++ kompatibel ist. Dies wurde erreicht, indem genau eine Übersetzungseinheit, die die Definition einer Inline-Funktion enthält, als diejenige angegeben werden muss, die die externe Definition für die Funktion bereitstellt. Da diese Spezifikation besteht einfach aus einer Erklärung , dass entweder das fehlt inlineSchlüsselwort oder enthält beide inlineund externes wird auch durch einen C ++ Übersetzer akzeptiert werden.

Durch Inlining in C99 wird die C ++ - Spezifikation auf zwei Arten erweitert. Wenn eine Funktion inlinein einer Übersetzungseinheit deklariert ist , muss sie nicht inlinein jeder anderen Übersetzungseinheit deklariert werden. Dies ermöglicht beispielsweise eine Bibliotheksfunktion, die innerhalb der Bibliothek eingebunden werden soll, aber nur über eine externe Definition an anderer Stelle verfügbar ist. Die Alternative, eine Wrapper-Funktion für die externe Funktion zu verwenden, erfordert einen zusätzlichen Namen. Dies kann sich auch nachteilig auf die Leistung auswirken, wenn ein Übersetzer keine Inline-Substitution durchführt.

Zweitens wird die Anforderung, dass alle Definitionen einer Inline-Funktion „genau gleich“ sein müssen, durch die Anforderung ersetzt, dass das Verhalten des Programms nicht davon abhängen sollte, ob ein Aufruf mit einer sichtbaren Inline-Definition oder der externen Definition von a implementiert wird Funktion. Auf diese Weise kann eine Inline-Definition auf ihre Verwendung innerhalb einer bestimmten Übersetzungseinheit spezialisiert werden. Beispielsweise kann die externe Definition einer Bibliotheksfunktion eine Argumentvalidierung enthalten, die für Aufrufe anderer Funktionen in derselben Bibliothek nicht erforderlich ist. Diese Erweiterungen bieten einige Vorteile. Programmierer, die sich Sorgen um die Kompatibilität machen, können sich einfach an die strengeren C ++ - Regeln halten.

Beachten Sie, dass es für Implementierungen nicht geeignet ist, Inline-Definitionen von Standardbibliotheksfunktionen in den Standardheadern bereitzustellen, da dies einen Legacy-Code beschädigen kann, der Standardbibliotheksfunktionen nach dem Einschließen ihrer Header neu deklariert. Das inlineSchlüsselwort soll Benutzern nur eine tragbare Möglichkeit bieten, das Inlining von Funktionen vorzuschlagen. Da die Standardheader nicht portierbar sein müssen, bieten Implementierungen andere Optionen wie:

#define abs(x) __builtin_abs(x)

oder andere nicht tragbare Mechanismen zum Inlinen von Standardbibliotheksfunktionen.

Jonathan Leffler
quelle
Danke, das ist sehr detailliert - und ungefähr so ​​lesbar wie der Standard selbst. :-) Ich wusste, dass mir etwas fehlen muss. Könnten Sie eine Referenz geben, woher Sie diese haben?
Sven Marnach
Mir ist
Sven Marnach
@Sven: Ich habe die URL von Ihrer Suche ausgeliehen und in die Antwort eingefügt. Das Dokument, das ich verwendet habe, war eine Kopie der Begründung, die ich zuvor (2005) gespeichert hatte, aber es war auch V5.10.
Jonathan Leffler
4
Danke noch einmal. Die wertvollste Erkenntnis, die ich aus der Antwort erhalten habe, ist, dass es ein Dokument gibt, das die Begründung des C99-Standards enthält (und wahrscheinlich gibt es auch Dokumente für andere Standards). Während Nemos Antwort mir einen Fisch gab, brachte mir dieser das Fischen bei.
Sven Marnach
0

> Ich erhalte einen Linkerfehler (undefinierter Verweis auf f)

Funktioniert hier: Linux x86-64, GCC 4.1.2. Kann ein Fehler in Ihrem Compiler sein; Ich sehe in dem zitierten Absatz nichts aus dem Standard, der das gegebene Programm verbietet. Beachten Sie die Verwendung von if anstelle von iff .

Eine Inline - Definition stellt eine Alternative zu einer externen Definition , was ein Übersetzer kann verwendet werden alle Anrufe an die Funktion in derselben Übersetzungseinheit zu implementieren.

Wenn Sie also das Verhalten der Funktion kennen fund sie in einer engen Schleife aufrufen möchten, können Sie ihre Definition kopieren und in ein Modul einfügen, um Funktionsaufrufe zu verhindern. oder Sie können eine Definition angeben, die für die Zwecke des aktuellen Moduls gleichwertig ist (überspringt jedoch die Eingabevalidierung oder eine beliebige Optimierung, die Sie sich vorstellen können). Der Compiler-Writer hat jedoch die Möglichkeit, stattdessen die Programmgröße zu optimieren.

Fred Foo
quelle
2
Der Standard besagt, dass der Compiler immer davon ausgehen kann, dass es eine externe Funktion mit demselben Namen gibt, und diese anstelle der Inline-Version aufruft. Ich denke also nicht, dass es sich um einen Compiler-Fehler handelt. Ich habe es mit gcc 4.3, 4.4 und 4.5 versucht, alle geben einen Linkerfehler.
Sven Marnach