Manchmal stelle ich fest, dass Programme auf meinem Computer mit dem Fehler "reiner virtueller Funktionsaufruf" abstürzen.
Wie kompilieren diese Programme überhaupt, wenn ein Objekt nicht aus einer abstrakten Klasse erstellt werden kann?
c++
polymorphism
virtual-functions
pure-virtual
Brian R. Bondy
quelle
quelle
doIt()
Aufruf im Konstruktor lässt sich leicht devirtualisieren undBase::doIt()
statisch weiterleiten , was nur einen Linkerfehler verursacht. Was wir wirklich brauchen, ist eine Situation, in der der dynamische Typ während eines dynamischen Versands der abstrakte Basistyp ist.Base::Base
Rufen Sie eine nichtf()
virtuelledoIt
Methode auf, die wiederum die (reine) virtuelle Methode aufruft .Neben dem Standardfall, eine virtuelle Funktion vom Konstruktor oder Destruktor eines Objekts mit rein virtuellen Funktionen aufzurufen, können Sie auch einen rein virtuellen Funktionsaufruf (zumindest unter MSVC) erhalten, wenn Sie eine virtuelle Funktion aufrufen, nachdem das Objekt zerstört wurde . Natürlich ist dies eine ziemlich schlechte Sache, aber wenn Sie mit abstrakten Klassen als Schnittstellen arbeiten und es vermasseln, ist es etwas, das Sie vielleicht sehen. Es ist möglicherweise wahrscheinlicher, wenn Sie referenzierte gezählte Schnittstellen verwenden und einen Ref-Count-Fehler haben oder wenn Sie eine Race-Bedingung für Objektverwendung / Objektzerstörung in einem Multithread-Programm haben ... Das Besondere an diesen Arten von Purecall ist, dass dies der Fall ist Oft ist es weniger einfach herauszufinden, was los ist, wenn die Überprüfung der "üblichen Verdächtigen" virtueller Anrufe in ctor und dtor sauber wird.
Um beim Debuggen dieser Art von Problemen zu helfen, können Sie in verschiedenen Versionen von MSVC den Purecall-Handler der Laufzeitbibliothek ersetzen. Sie tun dies, indem Sie Ihre eigene Funktion mit dieser Signatur versehen:
und verknüpfen Sie es, bevor Sie die Laufzeitbibliothek verknüpfen. Dies gibt Ihnen die Kontrolle darüber, was passiert, wenn ein Purecall erkannt wird. Sobald Sie die Kontrolle haben, können Sie etwas Nützlicheres als den Standard-Handler tun. Ich habe einen Handler, der einen Stack-Trace darüber liefern kann, wo der Purecall stattgefunden hat. Weitere Informationen finden Sie hier: http://www.lenholgate.com/blog/2006/01/purecall.html .
(Beachten Sie, dass Sie auch _set_purecall_handler () aufrufen können, um Ihren Handler in einigen Versionen von MSVC zu installieren.)
quelle
_purecall()
Aufruf, der normalerweise beim Aufrufen einer Methode einer gelöschten Instanz auftritt, erfolgt nicht , wenn die Basisklasse mit der__declspec(novtable)
Optimierung deklariert wurde (Microsoft-spezifisch). Damit ist es durchaus möglich, eine überschriebene virtuelle Methode aufzurufen, nachdem das Objekt gelöscht wurde, wodurch das Problem maskiert werden kann, bis es Sie in einer anderen Form beißt. Die_purecall()
Falle ist dein Freund!Normalerweise, wenn Sie eine virtuelle Funktion über einen baumelnden Zeiger aufrufen - höchstwahrscheinlich wurde die Instanz bereits zerstört.
Es kann auch "kreativere" Gründe geben: Vielleicht haben Sie es geschafft, den Teil Ihres Objekts abzutrennen, in dem die virtuelle Funktion implementiert wurde. Normalerweise wurde die Instanz jedoch bereits zerstört.
quelle
Ich bin auf das Szenario gestoßen, dass die rein virtuellen Funktionen wegen zerstörter Objekte aufgerufen werden,
Len Holgate
bereits eine sehr schöne Antwort haben , ich möchte mit einem Beispiel etwas Farbe hinzufügen:Der Destruktor der abgeleiteten Klasse hat die vptr-Punkte auf die vtable der Basisklasse zurückgesetzt, die die rein virtuelle Funktion hat. Wenn wir also die virtuelle Funktion aufrufen, ruft sie tatsächlich die rein virutalen auf.
Dies kann aufgrund eines offensichtlichen Codefehlers oder eines komplizierten Szenarios mit Race-Bedingungen in Multithreading-Umgebungen geschehen.
Hier ist ein einfaches Beispiel (g ++ - Kompilierung mit deaktivierter Optimierung - ein einfaches Programm kann leicht entfernt werden):
Und die Stapelverfolgung sieht aus wie:
Markieren:
Wenn das Objekt vollständig gelöscht ist, was bedeutet, dass der Destruktor aufgerufen wird und memroy zurückgefordert wird, erhalten wir möglicherweise einfach eine,
Segmentation fault
da der Speicher zum Betriebssystem zurückgekehrt ist und das Programm einfach nicht darauf zugreifen kann. Dieses "rein virtuelle Funktionsaufruf" -Szenario tritt normalerweise auf, wenn das Objekt im Speicherpool zugewiesen wird, während ein Objekt gelöscht wird, der zugrunde liegende Speicher vom Betriebssystem tatsächlich nicht zurückgefordert wird und der Prozess dort immer noch darauf zugreifen kann.quelle
Ich würde vermuten, dass aus irgendeinem internen Grund ein vtbl für die abstrakte Klasse erstellt wurde (es könnte für eine Art Laufzeit-Typ-Information erforderlich sein) und etwas schief geht und ein reales Objekt es bekommt. Es ist ein Fehler. Das allein sollte sagen, dass etwas, was nicht passieren kann, ist.
Reine Spekulation
edit: sieht so aus, als ob ich mich in dem fraglichen Fall irre. OTOH IIRC Einige Sprachen erlauben vtbl-Aufrufe aus dem Konstruktor-Destruktor.
quelle
Ich verwende VS2010 und wenn ich versuche, den Destruktor direkt von der öffentlichen Methode aus aufzurufen, wird zur Laufzeit der Fehler "Reiner virtueller Funktionsaufruf" angezeigt.
Also habe ich das, was sich in ~ Foo () befindet, verschoben, um die private Methode zu trennen, dann hat es wie ein Zauber funktioniert.
quelle
Wenn Sie Borland / CodeGear / Embarcadero / Idera C ++ Builder verwenden, können Sie dies einfach implementieren
Platzieren Sie beim Debuggen einen Haltepunkt im Code und sehen Sie den Aufrufstapel in der IDE. Andernfalls protokollieren Sie den Aufrufstapel in Ihrem Ausnahmehandler (oder dieser Funktion), wenn Sie über die entsprechenden Tools dafür verfügen. Ich persönlich benutze MadExcept dafür.
PS. Der ursprüngliche Funktionsaufruf befindet sich in [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
quelle
Hier ist ein hinterhältiger Weg, wie es passieren kann. Das ist mir heute im Wesentlichen passiert.
quelle
I had this essentially happen to me today
offensichtlich nicht wahr, weil einfach falsch: Eine reine virtuelle Funktion wird nur aufgerufen, wenncallFoo()
sie innerhalb eines Konstruktors (oder Destruktors) aufgerufen wird, da sich das Objekt zu diesem Zeitpunkt noch (oder bereits) im Stadium A befindet. Hier ist eine laufende Version Ihres Codes ohne den Syntaxfehler inB b();
- die Klammern machen es zu einer Funktionsdeklaration, Sie möchten ein Objekt.