c++ `vtable - g++undefinierter Verweis auf typeinfo




undefined reference (13)

Ich habe nur ein paar Stunden mit diesem Fehler verbracht, und während die anderen Antworten hier mir geholfen haben, zu verstehen, was vor sich ging, haben sie mein spezielles Problem nicht gelöst.

Ich arbeite an einem Projekt, das mit clang++ und g++ kompiliert. Ich hatte keine Verbindungsprobleme mit clang++ , aber bekam den undefined reference to 'typeinfo for Fehler mit g++ .

Der wichtige Punkt: Verknüpfen Reihenfolge MATTERS mit g++ . Wenn Sie die Bibliotheken typeinfo , die Sie in einer falschen Reihenfolge verknüpfen möchten, können Sie den typeinfo Fehler erhalten.

In dieser SO-Frage finden Sie weitere Informationen zur Verknüpfungsreihenfolge mit gcc / g++ .

Ich habe gerade den folgenden Fehler (und die Lösung online gefunden, aber es ist nicht in vorhanden):

(.gnu.linkonce. [stuff]): undefinierter Verweis auf [Methode] [Objektdatei] :(. gnu.linkonce. [stuff]): undefinierter Verweis auf `typeinfo für [Klassenname] '

Warum kann man einen dieser "undefinierten Verweis auf typeinfo" Linker Fehler bekommen?

(Bonuspunkte, wenn Sie erklären können, was hinter den Kulissen passiert.)


Dies kann auch passieren, wenn Sie -fno-rtti und -frtti code -frtti . Dann müssen Sie sicherstellen, dass jede Klasse, auf die type_info im -frtti Code -frtti wird, ihre Schlüsselmethode mit -frtti kompiliert -frtti . Ein solcher Zugriff kann auftreten, wenn Sie ein Objekt der Klasse erstellen, dynamic_cast usw. verwenden.

[ source ]


In meinem Fall habe ich eine Bibliothek von Drittanbietern mit Header-Dateien und so Datei verwendet. Ich unterklassifizierte eine Klasse, und Verbindungsfehler wie dieser trat auf, wenn ich versuche, meine Unterklasse zu instanziieren.

Wie von @sergiy erwähnt, konnte ich das Problem mit 'rtti' umgehen, indem ich die Konstruktorimplementierung in eine separate .cpp-Datei legte und '-fno-rtti' Kompilierflags auf die Datei anwendete . es läuft gut.

Da mir der interne Linkfehler noch nicht ganz klar ist, bin ich mir nicht sicher, ob meine Lösung allgemeingültig ist. Allerdings denke ich, dass es einen Versuch wert ist, bevor ich den Adapter-Weg ausprobiere, wie von @francois erwähnt. und natürlich, wenn alle Quellcodes verfügbar sind (nicht in meinem Fall), sollte man besser mit '-frtti' neu kompilieren, wenn möglich.

Noch eine Sache, wenn Sie versuchen, meine Lösung zu versuchen, versuchen Sie, die separate Datei so einfach wie möglich zu machen, und verwenden Sie nicht einige ausgefallene Funktionen von C ++. achte besonders auf Boost-bezogene Dinge, denn viel davon hängt von rtti ab.


Wenn Sie ein .so mit einem anderen verknüpfen, besteht eine weitere Möglichkeit darin, in gcc oder g ++ mit "-visibility = hidden" zu kompilieren. Wenn beide .so-Dateien mit "-visibility = hidden" erstellt wurden und sich die Schlüsselmethode nicht in derselben .so befindet wie eine andere Implementierung der virtuellen Funktion, sieht die letztere nicht die vtable oder typefinfo der ersten. Für den Linker sieht dies wie eine nicht implementierte virtuelle Funktion aus (wie in den Antworten von paxdiablo und cdleary).

In diesem Fall müssen Sie eine Ausnahme für die Sichtbarkeit der Basisklasse mit machen

__attribute__ ((visibility("default")))

in der Klassendeklaration. Zum Beispiel,

class __attribute__ ((visibility("default"))) boom{
    virtual void stick();
}

Eine andere Lösung ist natürlich, "-visibility = hidden" nicht zu verwenden. Das kompliziert die Dinge für den Compiler und den Linker, möglicherweise auf Kosten der Code-Leistung.


Die vorherigen Antworten sind korrekt, aber dieser Fehler kann auch dadurch verursacht werden, dass versucht wird, typeid für ein Objekt einer Klasse zu verwenden, die keine virtuellen Funktionen hat. C ++ RTTI benötigt eine vtable, so dass Klassen, für die Sie eine Typidentifikation durchführen möchten, mindestens eine virtuelle Funktion benötigen.

Wenn Sie möchten, dass Typinformationen für eine Klasse verwendet werden, für die Sie eigentlich keine virtuellen Funktionen möchten, machen Sie den Destruktor virtuell.


In der Basisklasse (eine abstrakte Basisklasse) deklarieren Sie einen virtuellen Destruktor und da Sie einen Destruktor nicht als reine virtuelle Funktion deklarieren können, müssen Sie entweder hier in der abstrakten Klasse definieren, nur eine Dummy-Definition wie virtual ~ base ( ) {} wird tun, oder in irgendeiner der abgeleiteten Klasse.

Wenn Sie dies nicht tun, landen Sie bei der Link-Zeit in einem "undefinierten Symbol". Da VMT einen Eintrag für alle reinen virtuellen Funktionen mit einem übereinstimmenden NULL enthält, wird die Tabelle abhängig von der Implementierung in der abgeleiteten Klasse aktualisiert. Aber für die nicht-reinen, aber virtuellen Funktionen benötigt es die Definition zur Verknüpfungszeit, damit es die VMT-Tabelle aktualisieren kann.

Verwenden Sie c ++ filt, um das Symbol zu minimieren. Wie $ c ++ filt _ZTIN10storageapi8BaseHostE wird so etwas wie "typeinfo für storageapi :: BaseHost" ausgeben.


Ich stoße auf eine Situation, die selten ist, aber dies kann anderen Freunden in einer ähnlichen Situation helfen. Ich muss mit gcc 4.4.7 an einem älteren System arbeiten. Ich muss Code mit Unterstützung von C ++ 11 oder höher kompilieren, also baue ich die neueste Version von gcc 5.3.0. Beim Erstellen meines Codes und Verknüpfen mit den Abhängigkeiten, wenn die Abhängigkeit mit einem älteren Compiler erstellt wird, habe ich einen 'undefinierten Verweis auf' Fehler erhalten, obwohl ich den Verbindungspfad eindeutig mit -L / pfad / zu / lib -llibname definiert habe. Einige Pakete wie Boost und Projekte, die mit cmake erstellt werden, neigen dazu, den älteren Compiler zu verwenden, und sie verursachen normalerweise solche Probleme. Sie müssen einen langen Weg gehen, um sicherzustellen, dass sie den neueren Compiler verwenden.


Dies tritt auf, wenn deklarierte (nicht reine) virtuelle Funktionen fehlende Körper sind. In Ihrer Klassendefinition etwas wie:

virtual void foo();

Sollte definiert werden (Inline oder in einer verknüpften Quelldatei):

virtual void foo() {}

Oder als rein virtuell deklariert:

virtual void foo() = 0;

Mögliche Lösungen für Code, der sich mit RTTI- und Nicht-RTTI-Bibliotheken befasst:

a) Kompiliere alles mit -frtti oder -fno-rtti neu
b) Wenn a) für Sie nicht möglich ist, versuchen Sie Folgendes:

Angenommen, libfoo wird ohne RTTI erstellt. Ihr Code verwendet libfoo und kompiliert mit RTTI. Wenn Sie in libfoo eine Klasse (Foo) mit virtuellen Objekten verwenden, stoßen Sie wahrscheinlich auf einen Link-Time-Fehler, der besagt, dass typefinfo für die Klasse Foo fehlt.

Definieren Sie eine andere Klasse (zB FooAdapter), die keine virtuelle hat und leitet die von Ihnen verwendeten Foo an Foo weiter.

Kompilieren Sie FooAdapter in einer kleinen statischen Bibliothek, die RTTI nicht verwendet und nur von libfoo-Symbolen abhängig ist. Geben Sie eine Überschrift dafür an und verwenden Sie diese stattdessen in Ihrem Code (der RTTI verwendet). Da FooAdapter keine virtuelle Funktion hat, wird es keine Typinfo haben und Sie können Ihre Binärdatei verlinken. Wenn Sie viele verschiedene Klassen von libfoo verwenden, ist diese Lösung vielleicht nicht bequem, aber es ist ein Anfang.


Ich habe denselben Fehler, wenn meine Schnittstelle (mit allen reinen virtuellen Funktionen) eine weitere Funktion benötigt und ich habe vergessen, sie "null" zu setzen.

ich hatte

class ICommProvider { public: /** * @brief If connection is established, it sends the message into the server. * @param[in] msg - message to be send * @return 0 if success, error otherwise */ virtual int vaSend(const std::string &msg) = 0; /** * @brief If connection is established, it is waiting will server response back. * @param[out] msg is the message received from server * @return 0 if success, error otherwise */ virtual int vaReceive(std::string &msg) = 0; virtual int vaSendRaw(const char *buff, int bufflen) = 0; virtual int vaReceiveRaw(char *buff, int bufflen) = 0; /** * @bief Closes current connection (if needed) after serving * @return 0 if success, error otherwise */ virtual int vaClose(); };

Letztes vaClose ist nicht virtuell, also kompiliert wusste nicht, wo man die Implementierung dafür bekommen kann und ist dadurch verwirrt worden. Meine Nachricht war:

... TCPClient.o :(. Rodata + 0x38): undefinierter Verweis auf `typeinfo für ICommProvider '

Einfacher Wechsel von

virtual int vaClose();

zu

virtual int vaClose() = 0;

behebt das Problem. ich hoffe es hilft


Ich habe gerade eine Menge dieser Fehler bekommen. Was passiert ist, ist, dass ich eine Header-Datei-only-Klasse in eine Header-Datei und eine cpp-Datei aufgeteilt habe. Allerdings habe ich mein Build-System nicht aktualisiert, daher wurde die cpp-Datei nicht kompiliert. Unter den einfach nicht definierten Verweisen auf die Funktionen, die im Header deklariert, aber nicht implementiert sind, habe ich viele dieser Typeinfo-Fehler bekommen.

Die Lösung bestand darin, das Build-System erneut auszuführen, um die neue cpp-Datei zu kompilieren und zu verknüpfen.


Ein möglicher Grund ist, dass Sie eine virtuelle Funktion deklarieren, ohne sie zu definieren.

Wenn Sie es deklarieren, ohne es in der gleichen Kompilierungseinheit zu definieren, geben Sie an, dass es an einer anderen Stelle definiert wurde. Dies bedeutet, dass die Linkerphase versucht, es in einer der anderen Kompilierungseinheiten (oder Bibliotheken) zu finden.

Ein Beispiel für die Definition der virtuellen Funktion ist:

virtual void fn() { /* insert code here */ }

In diesem Fall fügen Sie der Deklaration eine Definition hinzu, was bedeutet, dass der Linker sie später nicht auflösen muss.

Die Linie

virtual void fn();

deklariert fn() ohne es zu definieren und verursacht die Fehlermeldung, nach der Sie gefragt haben.

Es ist dem Code sehr ähnlich:

extern int i;
int *pi = &i;

was besagt, dass die ganze Zahl i in einer anderen Kompilierungseinheit deklariert ist, die zur Verbindungszeit aufgelöst werden muss (andernfalls kann pi nicht auf ihre Adresse gesetzt werden).


C ++ 98 und C ++ 03

Diese Antwort gilt für die älteren Versionen des C ++ - Standards. Die C ++ 11 und C ++ 14 Versionen des Standards enthalten formal keine 'Sequenzpunkte'; Operationen werden stattdessen 'sequenziert vor' oder 'nicht-sequenziert' oder 'unbestimmt sequenziert'. Der Nettoeffekt ist im Wesentlichen der gleiche, aber die Terminologie ist anders.

Haftungsausschluss : Okay. Diese Antwort ist ein bisschen lang. Also habe Geduld beim Lesen. Wenn du diese Dinge bereits kennst, wirst du sie nicht wieder verrückt machen.

Voraussetzungen : Grundkenntnisse in C ++ Standard

Was sind Sequenzpunkte?

Der Standard sagt

An bestimmten spezifizierten Punkten in der Ausführungssequenz, die als Sequenzpunkte bezeichnet werden , müssen alle Nebenwirkungen früherer Bewertungen vollständig sein, und es dürfen keine Nebenwirkungen nachfolgender Auswertungen auftreten. (§1.9 / 7)

Nebenwirkungen? Was sind Nebenwirkungen?

Die Auswertung eines Ausdrucks erzeugt etwas, und wenn sich zusätzlich der Zustand der Ausführungsumgebung ändert, wird gesagt, dass der Ausdruck (seine Bewertung) einige Nebeneffekte hat.

Beispielsweise:

int x = y++; //where y is also an int

Zusätzlich zur Initialisierungsoperation wird der Wert von y aufgrund der Nebenwirkung von ++ Operator geändert.

So weit, ist es gut. Weiter zu Sequenzpunkten. Eine Alternationsdefinition der Seq-Punkte, die vom Autor Steve Summit von comp.lang.c gegeben wurde:

Der Sequenzpunkt ist ein Zeitpunkt, zu dem sich der Staub abgesetzt hat und alle bisher beobachteten Nebenwirkungen garantiert sind.

Was sind die allgemeinen Sequenzpunkte, die im C ++ Standard aufgeführt sind?

Diese sind:

  • am Ende der Auswertung des vollständigen Ausdrucks ( §1.9/16 ) (Ein vollständiger Ausdruck ist ein Ausdruck, der kein Unterausdruck eines anderen Ausdrucks ist.) 1

Beispiel:

int a = 5; // ; is a sequence point here
  • bei der Bewertung jedes der folgenden Ausdrücke nach der Auswertung des ersten Ausdrucks ( §1.9/18 ) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (hier a, b ist ein Komma-Operator; in func(a,a++) , ist kein Komma-Operator, sondern nur ein Trennzeichen zwischen den Argumenten a und a++ . Daher ist das Verhalten in diesem Fall nicht definiert) (wenn a als primitiver Typ betrachtet wird)
  • bei einem Funktionsaufruf (unabhängig davon, ob die Funktion inline ist oder nicht), nach der Auswertung aller Funktionsargumente (falls vorhanden), die vor der Ausführung von Ausdrücken oder Anweisungen im Funktionskörper stattfinden ( §1.9/17 ).

1: Hinweis: Die Auswertung eines Full-Expressions kann die Auswertung von Teilausdrücken beinhalten, die nicht lexikalisch Teil des Full-Expressions sind. Beispielsweise werden Teilausdrücke, die an der Auswertung von Argumenten für Standardargumente (8.3.6) beteiligt sind, als in dem Ausdruck erstellt betrachtet, der die Funktion aufruft, und nicht als Ausdruck, der das Standardargument definiert

2: Die angegebenen Operatoren sind die eingebauten Operatoren, wie in Abschnitt 5 beschrieben. Wenn einer dieser Operatoren in einem gültigen Kontext überladen ist (Klausel 13) und somit eine benutzerdefinierte Operatorfunktion bezeichnet, bezeichnet der Ausdruck einen Funktionsaufruf und Die Operanden bilden eine Argumentliste ohne einen impliziten Sequenzpunkt zwischen ihnen.

Was ist ein nicht definiertes Verhalten?

Der Standard definiert nicht definiertes Verhalten in Abschnitt §1.3.12 als

Verhalten, wie es bei der Verwendung eines fehlerhaften Programmkonstrukts oder fehlerhafter Daten auftreten könnte, für die dieser Internationale Standard keine Anforderungen enthält 3 .

Undefiniertes Verhalten kann auch erwartet werden, wenn dieser Internationale Standard die Beschreibung einer expliziten Definition von Verhalten auslässt.

3: Zulässiges undefiniertes Verhalten reicht von einer vollständigen Ignorierung der Situation mit unvorhersagbaren Ergebnissen über ein Verhalten bei der Übersetzung oder Programmausführung in dokumentierter Weise für die Umgebung (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Abbruch einer Übersetzung oder Ausführung (mit der Ausgabe einer Diagnosemeldung).

Kurzum, undefiniertes Verhalten bedeutet, dass alles passieren kann, von Dämonen, die aus deiner Nase fliegen, bis deine Freundin schwanger wird.

Was ist die Beziehung zwischen nicht definiertem Verhalten und Sequenzpunkten?

Bevor ich darauf eingehe, müssen Sie die Unterschiede zwischen nicht definiertem Verhalten, nicht spezifiziertem Verhalten und implementiertem definiertem Verhalten kennen .

Sie müssen auch wissen, dass the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified .

Beispielsweise:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Ein anderes Beispiel here .

Jetzt sagt der Standard in §5/4

  • 1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf ein skalarer Gegenstand seinen gespeicherten Wert höchstens einmal durch die Auswertung eines Ausdrucks verändern.

Was heißt das?

Informell bedeutet dies, dass zwischen zwei Sequenzpunkten eine Variable nicht mehr als einmal geändert werden darf. In einer Ausdruckanweisung befindet sich der next sequence point normalerweise am abschließenden Semikolon und der previous sequence point befindet sich am Ende der vorherigen Anweisung. Ein Ausdruck kann auch Zwischensequenzpunkte enthalten.

Aus dem obigen Satz rufen die folgenden Ausdrücke Undefiniertes Verhalten auf:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Aber die folgenden Ausdrücke sind in Ordnung:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined
  • 2) Außerdem darf nur auf den früheren Wert zugegriffen werden, um den zu speichernden Wert zu bestimmen.

Was heißt das? Das bedeutet, wenn ein Objekt in einen vollständigen Ausdruck geschrieben wird, müssen alle Zugriffe darauf innerhalb desselben Ausdrucks direkt in die Berechnung des zu schreibenden Wertes einbezogen werden .

Zum Beispiel sind in i = i + 1 alle Zugriffe von i (in LHS und in RHS) direkt in die Berechnung des zu schreibenden Wertes involviert . So ist es in Ordnung.

Diese Regel schränkt legale Ausdrücke effektiv auf solche ein, in denen die Zugriffe nachweislich vor der Änderung stehen.

Beispiel 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Beispiel 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

ist nicht erlaubt, weil einer der Zugriffe von i (der in a[i] ) nichts mit dem Wert zu tun hat, der letztendlich in i gespeichert wird (was in i++ passiert), und deshalb gibt es keinen guten Weg zu definieren-- entweder für unser Verständnis oder den Compiler - ob der Zugriff vor oder nach dem Speichern des inkrementierten Wertes erfolgen soll. Das Verhalten ist also nicht definiert.

Beispiel 3:

int x = i + i++ ;// Similar to above

Folge Antwort here .





c++ linker g++