Funktionsimplementierungen und Kalssenimplementierungen im Header
-
Hallo Leute,
wenn ich zwei Dateien haben Datei1.cpp und Datei2.cpp swie einen Header Datei.h der in beiden includiert wird.
Wenn ich nun eine Funktion in den header schreibe
int f(int a) { return a+1; }dann bringt der Compiler ja eine Fehlermeldung die auch richtig ist.
Wenn ich das selbe mit einer Klasse mache funktioniert es.
class Ctest { Ctest(){} };Die Klasse wird ja in jedes obj eingefügt. Merkt das der Linker oder ist der Code der Klasse dann 2 mal in der exe. Wenn ich die Klasse einzeln in die cpp's kopiere und den Namen beibehalte aber den Construktor verschieden mache kompiliert er das auch obwohl die Klasse verschieden ist und den selebn Namen hat.
Ich würde sagen die Klasse ist dann 2 mal als Programmcode in der Exe enthalten?
MfG
-
Schau mal im Netz und in den FAQ nach dem Unterschied zwischen Deklaration und Definition, nach dem Modell, das hinter dem Linken verschiedener Übersetzungseinheiten steht usw.
Die Sache ist folgende: Eine Klassendefinition als solche erzeugt für sich genommen keinen wirklichen Code. Was den Code erzeugt sind die Funktionsdefinitionen und Methodendefinitionen. Wie du richtig bemerkt hast, gibts bei Definitionen der gleichen Funktion in mehreren Übersetzungseinheiten Linker-Fehler. Ausnahmen sind Instantiierungen von Funktionstemplates und inline-Funktionen. Wenn du f also als inline int f definiert hättest, wäre der Fehler auch nicht gekommen.
Bei deiner Klasse gibts keinen Fehler, weil es für Methoden, die direkt in der Klassendefinition definiert werden, eine Sonderregel gibt: sie sind implizit inline. Das ist der Grund, warum dein Ctest-Konstruktor beim Linken keine Probleme macht.
-
danke für die Antwort
Also sind die Methoden einer Klasse, die im header definiert werden automatisch inline. Das heißt auch das dieser Dunktionscode dann in der exe so oft vorhanden ist wie die Methode benutzt wurde?
Mann müßte wenn man den Programmcode so klein wie möglich haben will alle definitionen ins cpp file setzen.
MfG
-
Das heißt auch das dieser Funktionscode dann in der exe so oft vorhanden ist wie die Methode benutzt wurde oder optimiert das der linker raus?
-
Destiniy schrieb:
Also sind die Methoden einer Klasse, die im header definiert werden automatisch inline.
Jein. Sie sind implizit inline deklariert. Ob der Compiler eine Funktion wirklich inlined (d.h. den Code an jeden Aufruf direkt schreibt) oder ob er sie wie eine "normale" Funktion auslagert, ist Sache des Compilers, das kannst du kaum beeinflussen.
Mann müßte wenn man den Programmcode so klein wie möglich haben will alle definitionen ins cpp file setzen.
Jein. Wenn du die Funktionsdefinition ins .cpp setzt, machst du es zumindest dem Compiler unmöglich, den Funktionscode in anderen ÜEs direkt einzubinden. Du verhinderst damit nicht, dass der Compiler evtl. die Funktion in der selben ÜE inlined, in der sie definiert wurde. Du verhinderst auch nicht, dass ggf. der Linker Aufrufe der Funktion in anderen ÜEs inlined. (Moderne Linker können das zum Teil).
Wenn du möglichst geringen Programmcode haben möchtest (vermutlich, weil du extrem wenig Arbeitsspeicher zur Verfügung hast), solltest du schauen, ob der Compiler/Linker Optionen hat, bei dem er die Größe des Executable optimiert.
-
also angenommen der compiler macht keine von diesen funktionen inline.
Dann steht der Programmcode für die Methode ja auf jeden Fall in allen obj's. dessen cpp file den header includiert. Das heißt ein und der selbe Code ist in mehreren cpp dateien vorhanden.
Was macht der linker jetzt damit? Optimiert er den doppelten Code wieder raus?
Aber eigentlich weiß er an der _Stelle ja überhaupt nicht, dass Code doppelt vorhanden ist weil es dem ja egal ist was in den obj's ist er fügt es ja einfach nur zusammen.
-
inline int* foo() { static int bar; return &bar; }Sollte
&foooderfoo()davon abhängen, in welche ÜE der Ausdruck ausgewertet wird? Nein (C++98 7.1.2p4). Schlussfolgerung ist, dass der Linker die Definitionen verschmelzen muss.
-
Destiniy schrieb:
also angenommen der compiler macht keine von diesen funktionen inline.
Dann steht der Programmcode für die Methode ja auf jeden Fall in allen obj's. dessen cpp file den header includiert.
Ja, mal abgesehen vom Wort "Programmcode".
Das heißt ein und der selbe Code ist in mehreren cpp dateien vorhanden.
Nein, .cpps enthalten nur Quellcode, und der Quellcode der Funktion steht ja in der .h
Der Compiler macht aus dem Quellcode dann Objektcode (pro .cpp eine .obj-Datei), und es steht in jeder .obj-Datei, deren .cpp deine .h eingebunden hat, der Objektcode der besagten Funktion.Was macht der linker jetzt damit? Optimiert er den doppelten Code wieder raus?
Aber eigentlich weiß er an der _Stelle ja überhaupt nicht, dass Code doppelt vorhanden ist weil es dem ja egal ist was in den obj's ist er fügt es ja einfach nur zusammen.
Jein, ganz so dumm ist der Linker dann doch nicht. Der Compiler schreibt an den Objektcode einen Hinweis für den Linker, dass der Objektcode mehrfach vorkommen kann (damit der Linker keinen ODR-Fehler schmeißt). Der Linker darf sich dann eines der Vorkommen aussuchen, um den endgültigen Programmcode zu schreiben (vermutlich das erste was ihm über den Weg läuft) und den Rest ignorieren.
Das kann übrigens zu einer bösen Falle werden: nehmen wir an, du hast in einer ÜE eine Funktion
inline void foo(Bar&)definiert, die das übergebene Bar auf gewisse Weise ändert. In einer ganz anderen ÜE hast du eine andere Funktion mit gleichem Namen und identischer Signatur definiert, die am übergebenen Bar etwas völlig anderes macht. Was heißt das für den Linker? Er findet eine Definition für foo und benutzt sie immer, er kann beide Funktionen nicht voneinander unterscheiden, für ihn sind sie die selbe Funktion. Du wirst immer das Verhalten der einen Funktion beobachten, auch wenn die andere vom Compiler "eingesetzt" wurde. Es sei denn der Compiler hat geinlined...Ich hatte sowas ähnliches mal mit zwei Hilfsklassen gleichen Namens, die der Entwickler leider nicht mit interner Linkage versehen hat:
//a.cpp class Helper { int a; std::string b; std::string c; public: Helper() : a(10), b("foo"), c("bar") {} }; /* ... */ //b.cpp class Helper { int a; std::string b; public: Helper() : a(10), b("foobar"){} }; void dostuff() { Helper h; //BÄM! }Das Ding ist, nachdem jemand in a.cpp den zweiten String als Member zum Helper hinzugefügt hat, immer abgeraucht. Beim Debuggen kam man irgendwo in einem std::string Konstruktor auf eine access violation. Der Grund war am Ende folgender: der Linker hatte für die (inline!)-Funktion Helper::Helper() das erste Vorkommen gewählt, was ihm über den Weg lief (das aus a.cpp), um den Helper in dostuff() zu initialisieren. Der initialisiert aber die Variable c, die in dem Helper h garnicht vorhanden ist, der Compiler hatte dem Ding ja nur Speicher für den int und einen String reserviert. Der zweite String wurde also irgendwo im Nirvana konstruiert, daher die AV. Sowas zu finden ist eklig...