it-swarm.dev

Warum können Sie die Methodendefinition in der Header-Datei in C ++ haben, während Sie dies in C nicht können?

In C kann die Funktionsdefinition/-implementierung nicht in der Headerdatei enthalten sein. In C++ können Sie jedoch eine vollständige Methodenimplementierung in der Header-Datei durchführen. Warum ist das Verhalten anders?

25
Joshua Partogi

Wenn Sie in C eine Funktion in einer Header-Datei definieren, wird diese Funktion in jedem kompilierten Modul angezeigt, das diese Header-Datei enthält, und ein öffentliches Symbol wird für die Funktion exportiert. Wenn also die Funktion additup in header.h definiert ist und foo.c und bar.c beide header.h enthalten, enthalten foo.o und bar.o Kopien von additup.

Wenn Sie diese beiden Objektdateien miteinander verknüpfen, sieht der Linker, dass die Symboladdition mehr als einmal definiert ist und dies nicht zulässt.

Wenn Sie die Funktion als statisch deklarieren, wird kein Symbol exportiert. Die Objektdateien foo.o und bar.o enthalten weiterhin separate Kopien des Codes für die Funktion, und sie können sie verwenden, aber der Linker kann keine Kopie der Funktion sehen, also werde mich nicht beschweren. Natürlich kann auch kein anderes Modul die Funktion sehen. Und Ihr Programm wird mit zwei identischen Kopien derselben Funktion aufgebläht.

Wenn Sie die Funktion nur in der Header-Datei deklarieren, aber nicht definieren und dann in nur einem Modul definieren, sieht der Linker eine Kopie der Funktion, und jedes Modul in Ihrem Programm kann sie sehen und benutze es. Und Ihr kompiliertes Programm enthält nur eine Kopie der Funktion.

Sie können die Funktionsdefinition in der Header-Datei in C haben, es ist nur ein schlechter Stil, eine schlechte Form und eine rundum schlechte Idee.

(Mit "deklarieren" meine ich die Bereitstellung eines Funktionsprototyps ohne Körper; mit "definieren" meine ich den tatsächlichen Code des Funktionskörpers; dies ist die Standard-C-Terminologie.)

29
David Conrad

C und C++ verhalten sich in dieser Hinsicht sehr ähnlich - Sie können inline Funktionen in Headern haben. In C++ ist jede Methode, deren Hauptteil sich innerhalb der Klassendefinition befindet, implizit inline. Wenn Sie dasselbe in C tun möchten, deklarieren Sie die Funktionen static inline.

30
Simon Richter

Das Konzept einer Header-Datei bedarf einer kleinen Erklärung:

Entweder geben Sie eine Datei in der Befehlszeile des Compilers ein oder führen ein '#include' aus. Die meisten Compiler akzeptieren eine Befehlsdatei mit der Erweiterung c, C, cpp, c ++ usw. als Quelldatei. Sie enthalten jedoch normalerweise eine Befehlszeilenoption, um die Verwendung einer beliebigen Erweiterung einer Quelldatei zu ermöglichen.

Im Allgemeinen heißt die in der Befehlszeile angegebene Datei "Quelle" und die enthaltene Datei "Kopfzeile".

Der Präprozessor-Schritt nimmt sie alle und lässt alles für den Compiler wie eine einzige große Datei erscheinen. Was sich in der Kopfzeile oder in der Quelle befand, ist an dieser Stelle eigentlich nicht relevant. Normalerweise gibt es eine Option eines Compilers, der die Ausgabe dieser Stufe anzeigen kann.

Für jede Datei, die in der Compiler-Befehlszeile angegeben wurde, wird dem Compiler eine große Datei übergeben. Dies kann Code/Daten enthalten, die Speicher belegen und/oder ein Symbol erstellen, auf das aus anderen Dateien verwiesen wird. Jetzt erzeugt jeder von diesen ein 'Objekt'-Bild. Der Linker kann ein "doppeltes Symbol" angeben, wenn dasselbe Symbol in mehr als zwei Objektdateien gefunden wird, die miteinander verknüpft werden. Vielleicht ist das der Grund; Es wird nicht empfohlen, Code in eine Header-Datei einzufügen, wodurch Symbole in der Objektdatei erstellt werden können.

Die 'Inline' sind normalerweise inline, aber beim Debuggen werden sie möglicherweise nicht inline. Warum gibt der Linker keine mehrfach definierten Fehler aus? Einfach ... Dies sind "schwache" Symbole. Solange alle Daten/Codes für ein schwaches Symbol aller Objekte dieselbe Größe und denselben Inhalt haben, behält der Link eine Kopie und löscht eine Kopie von anderen Objekten. Es klappt.

7
vrdhn

Sie können dies in C99 tun: inline -Funktionen werden garantiert an einer anderen Stelle bereitgestellt. Wenn also eine Funktion nicht inline ist, wird ihre Definition in eine Deklaration übersetzt (d. H. Die Implementierung wird verworfen). Und natürlich können Sie static verwenden.

4
SK-logic

C++ - Standardzitate

Der C++ 17 N4659 Standardentwurf 10.1.6 "Der Inline-Spezifizierer" besagt, dass Methoden implizit inline sind:

4 Eine in einer Klassendefinition definierte Funktion ist eine Inline-Funktion.

und dann weiter unten sehen wir, dass Inline-Methoden nicht nur können, sondern muss auf allen Übersetzungseinheiten definiert werden können:

6 Eine Inline-Funktion oder -Variable muss in jeder Übersetzungseinheit definiert sein, in der sie verwendet wird, und muss in jedem Fall genau dieselbe Definition haben (6.2).

Dies wird auch ausdrücklich in einem Hinweis unter 12.2.1 "Mitgliedsfunktionen" erwähnt:

1 Eine Mitgliedsfunktion kann in ihrer Klassendefinition definiert sein (11.4). In diesem Fall handelt es sich um eine Inline-Mitgliedsfunktion (10.1.6) [...]

3 [Hinweis: In einem Programm kann höchstens eine Definition einer Nicht-Inline-Elementfunktion vorhanden sein. In einem Programm kann es mehr als eine Inline-Elementfunktionsdefinition geben. Siehe 6.2 und 10.1.6. - Endnote]

Implementierung von GCC 8.3

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

Symbole kompilieren und anzeigen:

g++ -c main.cpp
nm -C main.o

ausgabe:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

dann sehen wir aus man nm dass die MyClass::myMethod Das Symbol wird in den ELF-Objektdateien als schwach markiert, was bedeutet, dass es in mehreren Objektdateien angezeigt werden kann:

"W" "w" Das Symbol ist ein schwaches Symbol, das nicht speziell als schwaches Objektsymbol gekennzeichnet wurde. Wenn ein schwach definiertes Symbol mit einem normal definierten Symbol verknüpft ist, wird das normal definierte Symbol fehlerfrei verwendet. Wenn ein schwaches undefiniertes Symbol verknüpft ist und das Symbol nicht definiert ist, wird der Wert des Symbols systemspezifisch fehlerfrei ermittelt. Auf einigen Systemen gibt Großbuchstaben an, dass ein Standardwert angegeben wurde.