Stellen Sie sich vor, wir haben drei .h
Dateien:
f.h
:
g.h
:
h.h
:
Nun haben wir auch drei .cpp
Dateien:
f.cpp
:
g.cpp
:
h.cpp
:
Das Kompilieren gibt uns f.o
, g.o
und h.o
. Jetzt werfen wir dieses main.cpp
:
A-a-und machen wir g++ main.cpp f.o g.o h.o
. Jetzt kommt meine eigentliche Überraschung: Da diese drei .o
-Dateien drei verschiedene Definitionen für int Class<int>::id(int)
enthalten, erwarte ich einen Verknüpfungsfehler. Was ich jedoch bekomme, ist eine funktionierende a.out
, die 1 2 3
ausdruckt. Und wenn ich .o
Dateien im Befehl neu anordne, wird 101 102 103
gedruckt.
Und nun zu den eigentlichen Fragen: Wie genau führt der Linker in diesem Fall eine Verknüpfung aus? Wie ermittelt es, welche Instanziierung von Class<int>
zu behalten und was wegzuwerfen ist? Und warum beschwert es sich nicht über mehrere Definitionen?
nm
gibt folgende Ausgabe für nm f.o g.o h.o
:
Offensichtlich importieren f.o
und g.o
sowohl das Symbol% co_de% als auch __ZN5ClassIiE2idEi
dieses Symbol (Großbuchstaben bedeuten externe Verknüpfung). Und es führt zu keinen Fehlern. Warum?
Dieses Problem ist eigentlich bekannt: Sie verletzen die ODR. Aufgrund der Art der Vorlagen merken der Compiler und der Linker das nicht. Was passiert, ist folgendes:
Der Linker nimmt an , dass der ODR NICHT verletzt wurde und daher frei ist, JEDE der Instanziierungen des Templates zu verwenden (alias alle Instanziierungen des Templates mit denselben Parametern führen zu genau demselben) Code wird generiert). Dieses System ist sinnvoll, solange Sie das ODR nicht verletzen.
Nun wählt der Linker in Ihrem Fall die erste Instanz, die er erhält, was dazu führt, dass das Ergebnis von der Reihenfolge der Verknüpfung abhängt.
Klingt zu offensichtlich, aber Ihre Klasse verwendet implizites Inlining. Das heißt, wann immer Sie den Quellcode in die Klassendeklaration einfügen, weisen Sie darauf hin, dass Sie es inline wollen. Also ...
Die einzigen Dinge, die der Linker sehen wird, sind f (), g () und h ().
Es könnte interessant sein, Ihren Code mit etwas Ähnlichem zu wiederholen, obwohl es immer noch so einfach ist, könnte der Compiler es einfach inline einbinden (abhängig von Ihrem Compiler).
%Vor%