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

23

In C können Sie die Funktionsdefinition / -implementierung nicht in der Header-Datei haben. In C ++ können Sie jedoch eine vollständige Methodenimplementierung in der Header-Datei haben. Warum ist das Verhalten anders?

Joshua Partogi
quelle

Antworten:

28

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 beide Kopien von additup.

Wenn Sie diese beiden Objektdateien miteinander verknüpfen, stellt der Linker fest, dass das Symbol additup mehrmals definiert wurde und lässt dies nicht zu.

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

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

Sie können also 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, dass ein Funktionsprototyp ohne Textkörper angegeben wird. Mit "Definieren" meine ich, dass der tatsächliche Code des Funktionskörpers angegeben wird. Dies ist die Standard-C-Terminologie.)

David Conrad
quelle
2
Es ist keine so schlechte Idee - diese Art von Dingen findet man sogar in den GNU Libc-Headern.
SK-logic
aber was ist mit dem idiomatischen Umbruch der Header-Datei in einer Direktive für bedingte Kompilierung? Selbst wenn die Funktion als AND deklariert ist, wird sie nur einmal geladen. Ich bin neu in C, daher kann es zu Missverständnissen kommen.
user305964
2
@papiro Das Problem ist, dass Wrapping nur während eines einzigen Compilerlaufs schützt. Wenn also foo.c in einem Durchlauf zu foo.o kompiliert wird, werden bar.c zu bar.o in einem anderen Durchlauf und foo.o und bar.o in einem dritten (wie es typisch ist), diesem Wrapping, zu a.out verknüpft verhindert nicht mehrere Instanzen davon, eine in jeder Objektdatei.
David Conrad
Ist das hier beschriebene Problem nicht das, was #ifndef HEADER_Hverhindert werden soll?
Robert Harvey
27

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

Simon Richter
quelle
" Deklarieren Sie die Funktionenstatic inline " ... und Sie haben immer noch mehrere Kopien der Funktion in jeder Übersetzungseinheit, die sie verwendet. In C ++ ohne static inlineFunktion haben Sie nur eine Kopie. Um die Implementierung tatsächlich im Header in C zu haben, müssen Sie 1) die Implementierung als inline(z. B. ) markieren inline void func(){do_something();}und 2) tatsächlich angeben, dass diese Funktion in einer bestimmten Übersetzungseinheit (z void func();. B. ) ausgeführt wird.
Ruslan
6

Das Konzept einer Header-Datei bedarf einer kleinen Erläuterung:

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

Im Allgemeinen heißt die in der Befehlszeile angegebene Datei 'Source' und die enthaltene Datei 'Header'.

Der Präprozessor-Schritt nimmt sie alle und lässt alles für den Compiler wie eine einzige große Datei erscheinen. Was in der Kopfzeile oder in der Quelle war, ist an dieser Stelle eigentlich nicht relevant. Es gibt normalerweise 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 den 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, da dies zu Symbolen in der Objektdatei führen kann.

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

vrdhn
quelle
3

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

SK-Logik
quelle
1

C ++ - Standardzitate

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

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

und weiter unten sehen wir, dass Inline-Methoden nicht nur für alle Übersetzungseinheiten definiert werden können, sondern müssen :

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

Dies wird auch in einem Hinweis unter 12.2.1 "Mitgliedsfunktionen" explizit erwähnt:

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

3 [Hinweis: In einem Programm kann es höchstens eine Definition einer Nicht-Inline-Member-Funktion geben. Es kann mehr als eine Inline-Member-Funktionsdefinition in einem Programm 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, man nmdass das MyClass::myMethodSymbol in den ELF-Objektdateien als schwach markiert ist, was bedeutet, dass es in mehreren Objektdateien auftreten 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 ohne Fehler verwendet. Wenn ein schwaches undefiniertes Symbol verknüpft ist und das Symbol nicht definiert ist, wird der Wert des Symbols systemspezifisch ohne Fehler bestimmt. Bei einigen Systemen gibt Großbuchstaben an, dass ein Standardwert angegeben wurde.

Ciro Santilli ist ein Schauspieler
quelle
-4

Vermutlich aus demselben Grund, aus dem Sie die vollständige Methodenimplementierung in die Klassendefinition in Java einfügen müssen.

Sie sehen vielleicht ähnlich aus, mit verwinkelten Klammern und vielen der gleichen Schlüsselwörter, aber sie sind verschiedene Sprachen.

Paul Butcher
quelle
1
Nein, es gibt tatsächlich eine echte Antwort, und eines der
Ed S.
4
Nein, es wurde entwickelt, um "einen hohen Grad an C-Kompatibilität" und "keine unbeabsichtigten Inkompatibilitäten mit C" zu haben. (beide von Stroustrup). Ich stimme zu, dass eine eingehendere Antwort gegeben werden könnte, um zu verdeutlichen, warum diese besondere Unvereinbarkeit nicht unbegründet ist. Fühlen Sie sich frei, eine zu liefern.
Paul Butcher
Ich hätte es getan, aber Simon Richter hatte es bereits getan, als ich das gepostet habe. Wir können über den Unterschied zwischen "abwärtskompatibel" und "A High Degree of C Compatibility" streiten, aber die Tatsache bleibt, dass diese Antwort falsch ist. Die letzte Aussage wäre richtig, wenn wir C # und C ++ vergleichen würden, aber nicht so sehr mit C und C ++.
Ed S.