Wie finde ich doppelte Definitionen aus Vorlagenspezialisierungen?

8

Ich habe eine Template-Klasse mit einer Spezialisierung, die in einer anderen Datei definiert ist. Daher ist es möglich, zwei Versionen derselben Klasse zu generieren: Einmal durch Ersetzen des Template-Parameters und einmal durch Verwenden der Spezialisierung. Mein derzeitiges Verständnis ist, dass dies dazu führen kann, dass zwei Instanzen desselben Typs unterschiedliche Größen im Speicher haben, was zu Segmentierungsfehlern führt.

Ich habe ein minimales Beispiel erstellt und der folgende Code soll die Frage veranschaulichen:

Erstellen Sie eine Vorlagenklasse:

%Vor%

Vorlagenspezialisierung in einer anderen Datei:

%Vor%

Haben Sie eine Klasse, die nur die Template-Klassendefinition enthält, aber sollte die Spezialisierung einschließen.

%Vor%

Die Hauptklasse kennt nun zwei Versionen von Example<int> die von A und die von templatespecialization.h.

%Vor%

Bitte beachten Sie, dass dieses Problem nur auftritt, wenn Klasse A separat kompiliert wird, dieses Beispiel von ideone outputs 6, während die Verwendung separater Dateien zu einer Segmentierung führen kann, oder Ausgabe 42 ( Ссылка ). Das Beispiel kompiliert auf meinem Computer erfolgreich und gibt 2013265920 :

aus

In der Produktionsversion des obigen Beispiels ist alles in eine gemeinsame Bibliothek eingebaut, die von main benutzt wird.

Frage 1: Warum erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Frage 2: Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen, um mehrere Implementierungen des gleichen Typs wie im obigen Beispiel zu erkennen?

Bearbeiten: Bitte beachten: Der obige Code ist ein minimales Beispiel, um das Problem zu erklären. Der Grund für die Situation ist, dass die Vorlagenklasse aus einer Bibliothek stammt und ich die Dateien aus dieser Bibliothek nicht bearbeiten kann. Schließlich wird das Ganze überall in der ausführbaren Datei verwendet und jetzt muss ich herausfinden, ob das obige Problem auftritt.

Edit: Code oben kann so kompiliert werden:

%Vor%     
Beginner 08.01.2015, 09:25
quelle

4 Antworten

7

Sie haben unterschiedliche Definitionen derselben Vorlage und ihrer Spezialisierungen in verschiedenen Übersetzungseinheiten. Dies führt zu einer Regel mit einer Definitionsregel .

Ein Fix wäre, die Spezialisierung in dieselbe Header-Datei zu legen, in der die primäre Klassenvorlage definiert ist.

  

Frage 1: Warum erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Verschiedene Typen können die gleiche Größe haben (z. B. double und int64_t ), so dass es offensichtlich nicht funktioniert, nur Größen von Objekten zu vergleichen.

  

Frage 2: Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen, um mehrere Implementierungen des gleichen Typs wie im obigen Beispiel zu erkennen?

Sie sollten gold linker zum Verknüpfen Ihrer C ++ - Anwendungen verwenden, wenn Sie es nicht bereits verwenden. Eine nette Eigenschaft davon ist --detect-odr-violations Befehlszeilenschalter, der genau das tut, was Sie fragen:

  

gold verwendet eine Heuristik, um mögliche ODR-Verletzungen zu finden: Wenn dasselbe Symbol in zwei verschiedenen Eingabedateien definiert ist und die beiden Symbole unterschiedliche Größen haben, dann schaut gold auf die Debugging-Informationen in den Eingabeobjekten. Wenn die Debugging-Informationen darauf hindeuten, dass die Symbole in verschiedenen Quelldateien definiert wurden, meldet gold eine mögliche ODR-Verletzung. Dieser Ansatz hat sowohl falsche als auch falsche positive Ergebnisse. Es ist jedoch einigermaßen zuverlässig beim Erkennen von Problemen, wenn nicht optimierter Code verknüpft wird. Es ist viel einfacher, diese Probleme bei der Link-Zeit zu finden, als Fälle mit dem falschen Symbol zu beheben.

Weitere Informationen finden Sie in der Durchsetzung einer Definitionsregel Details.

    
Maxim Egorushkin 13.01.2015, 12:52
quelle
1
  

Frage 1 : Erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Weil dies kein Linkerproblem ist. Bei Vorlagen sollten die Hauptdeklaration und alle anderen Spezialisierungen (sei es Klasse oder Funktion) im Vordergrund sichtbar sein.

  

Frage 2 : Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen, um mehrere Implementierungen desselben Typs zu erkennen?   wie im obigen Beispiel?

Zumindest ist mir nichts bekannt.

Um diese Situation weiter zu vereinfachen, sehen Sie sich einen ähnlichen Code an:

%Vor%

Es ist möglich, dass Sie basierend auf der Art der Kompilierung unterschiedliche Ergebnisse erhalten.

Aktualisieren :
Mehrere Bodendefinitionen für dieselbe Funktion haben ein undefiniertes Verhalten . Das ist der Grund, warum du eine peinliche Ausgabe wie 2013265920 bekommst und das gleiche passiert auch in meiner Maschine. Die Ausgabe sollte entweder 42 oder 6 sein. Ich habe Ihnen das obige Beispiel gegeben, denn mit inline functions können Sie solche Race Conditions erstellen.

Mit meinem begrenzten Wissen über die Verknüpfungsstufe ist die Verantwortung, die von einem typischen Linker übernommen wird, nur begrenzt, wenn mehr als 1 nicht-inline Funktionsdefinitionen mit derselben Signatur abgelehnt werden. z.B.

%Vor%

Darüber hinaus überprüft es nicht, ob der Funktionskörper ähnlich ist oder nicht, da dieser bereits während der früheren Phase analysiert wurde und der Linker den Funktionskörper nicht analysiert.
Nachdem Sie oben gesagt haben, achten Sie jetzt auf diese Aussage:

  

template Funktionen sind von Natur aus immer inline

Daher kann Linker keine Chance haben, sie zurückzuweisen.

Der sicherste Weg besteht darin, den schreibgeschützten Header in Ihren spezialisierten Header zu integrieren und diesen spezialisierten Header überall einzubeziehen.

    
iammilind 13.01.2015 09:58
quelle
0

Ich kenne keine Möglichkeit, dies durch Analyse der kompilierten Binärdatei zu machen, aber Sie könnten ein Diagramm der #include -Beziehungen Ihres Programms erstellen - es gibt Werkzeuge, die das können, wie Doxygen - und sie verwenden um nach Dateien zu suchen, die (direkt oder indirekt) den Bibliothekskopf, aber nicht den Spezialisierungskopf enthalten.

Sie müssen jede Datei untersuchen, um festzustellen, ob sie tatsächlich die fragliche Vorlage verwendet, aber Sie können zumindest die Menge der Dateien eingrenzen, die Sie untersuchen müssen.

    
Wyzard 08.01.2015 14:37
quelle
0

Ich glaube, Sie haben es geschafft, den Compiler zu täuschen. Aber ich sollte beachten, dass Sie meiner Meinung nach die Konzeption von Vorlagen auf eine falsche Art und Weise verstehen, insbesondere versuchen Sie, die Template-Spezialisierung mit der Vererbung zu verwechseln. Ich meine, die Template-Spezialisierung darf keine Datenelemente zur Klasse hinzufügen, die einzigen Ziele sind die Definition von Typen für Funktionsparameter und Klassenfelder. Wenn Sie Algorithmen ändern, d. H. Code umschreiben oder der Klasse neue Datumselemente hinzufügen möchten, sollten Sie die abgeleitete Klasse definieren. Bezüglich "mehrstufiger" separater Kompilierungs- und Vorlagenklassen in der Bibliothek sagte C ++ - Referenz ( Ссылка ):

  

Da Vorlagen bei Bedarf kompiliert werden, erzwingt dies eine Einschränkung für Projekte mit mehreren Dateien: Die Implementierung (Definition) einer Vorlagenklasse oder -funktion muss sich in derselben Datei wie ihre Deklaration befinden. Das bedeutet, dass wir die Schnittstelle nicht in einer separaten Headerdatei trennen können und dass sowohl die Schnittstelle als auch die Implementierung in jede Datei eingeschlossen werden müssen, die die Vorlagen verwendet.   Da kein Code generiert wird, bis eine Vorlage bei Bedarf instanziiert wird, sind Compiler bereit, die Aufnahme mehrerer Vorlagendateien mit Deklarationen und Definitionen in ein Projekt zuzulassen, ohne dass Verknüpfungsfehler auftreten.

    
VolAnd 16.01.2015 12:25
quelle

Tags und Links