Tutorials
Weblinks
- About C, C++ and C#
- Andrei Alexandrescu
- Artima Weblogs
- Bjarne Stroustrup
- Boost C++ Libraries
- C++ auf kompf.de
- C++ Language Tutorial
- C++ Soup!
- C++ Tutorial
- C++.de
- Clean Code Developer (CCD)
- Coding the Wheel
- Dr. Dobb's Portal
- Funsoft Forum
- Gesellschaft für Systems Engineering e.V.
- Mark's Blog (Mark Russinovich)
- Martins Blog (Martin Richter, MVP)
- Scott Meyers
- SGI® STL Programmer’s Guide
- Software Dev-Blog
- Software Engineering Radio
- Software Engineering Roundup
- Sutter's Mill (Herb Sutter)
- Thinking Asynchronously in C++
Verwandte Beiträge
Neueste Artikel
Meist gelesen
Social Bookmarking
| Pure virtual function call |
|
|
|
| Freitag, den 09. April 2010 um 14:37 Uhr |
|
"R6025 - Pure virtual function call" war die letzte Meldung des gerade abgestürzten und bis zu diesem Zeitpunkt anscheinend fehlerfrei laufenden Programms. In diesem Blog werde ich mögliche Ursachen für dieses Problem aufzeigen und Tipps zur Fehlersuche geben. Die Fehlermeldung "Pure virtual function call" sagt exakt das aus, was auch tatsächlich passiert ist: es ist versucht worden, eine rein virtuelle Funktion, genauer: Methode, einer Klasse aufzurufen. Zur Erinnerung: eine virtuelle Methode ist eine Methode einer Klasse, deren Einsprungadresse erst zur Laufzeit ermittelt wird (sog. late binding). Bei den rein virtuellen Methoden in C++ setzt man diese Methoden explizit gleich null, wodurch die Klasse automatisch zu einer abstrakten Klasse wird, d.h. es können von ihr keine Instanzen erzeugt werden. Hier ein Beispiel: class AbstractClass
{ public: AbstractClass(void) { }; virtual ~AbstractClass(void) { }; protected: virtual bool thePureVirtualFunction(void) = 0; }; Würde man nun versuchen, von der Klasse 1>.\main.cpp(5) : error C2259: 'AbstractClass' : cannot instantiate abstract class Der designierte Sinn und Zweck von abstrakten Klassen ist es ja, die Rolle einer Basisklasse einzunehmen und in einer Vererbungshierarchie grundlegende Eigenschaften von Unterklassen festzulegen. Entwickler müssen daher in den Klassen, die von einer abstrakten Klasse abgeleitet sind, alle vererbten abstrakten Methoden überschrieben und implementieren, damit die erbende Klasse selbst nicht abstrakt ist: class Base
{ public: Base(void) { }; virtual ~Base(void) { }; protected: virtual bool thePureVirtualFunction(void) = 0; }; class Derived : public Base { public: Derived(void) { }; virtual ~Derived(void) { }; protected: virtual bool thePureVirtualFunction(void) { // Do expedient things here... return true; }; }; Versuchen wir nun irgendwo in unserem Programm ein Objekt der Klasse int main(int argc, char *argv[])
{ Derived obj; // OK! return 0; }; So weit, so gut. Kommen wir aber nun wieder zurück zu unserem eigentlichen Problem, dem "pure virtual function call". Als erstes werden wir nun unser vorheriges Beispiel modifizieren und versuchen, in dem Basisklassen-Konstruktor von class Base
{ public: // Attempt to call the pure virtual function directly here... Base(void) { thePureVirtualFunction(); }; // ...remaining code as in the previous code sample... }; Was nun passiert, ist abhängig vom Compiler. Das Microsoft Visual-Studio 2008 beispielsweise meldet einen Linker-Fehler für error LNK2001: unresolved external symbol Der Microsoft-Compiler ignoriert in diesem Fall jegliche Polymorphie zur Laufzeit und behandelt den Aufruf von Den gleichen Linker-Fehler erhalten wir, wenn wir den Aufruf im Destruktor von virtual ~Base(void) { thePureVirtualFunction(); };
Auf diese Art und Weise können wir anscheinend den Fehler zur Laufzeit nicht provozieren, da schon der Compiler bzw. Linker hier einen Fehler erkennt. Versuchen wir stattdessen doch nun einmal, die virtuelle Methode indirekt aufzurufen, indem wir im Konstruktor von class Base
{ public: Base(void) { doSomething(); }; virtual ~Base(void) { }; protected: void doSomething(void) { thePureVirtualFunction(); }; virtual bool thePureVirtualFunction(void) = 0; }; class Derived : public Base { public: Derived(void) { }; virtual ~Derived(void) { }; protected: virtual bool thePureVirtualFunction(void) { // Do expedient things here... return true; }; }; int main(int argc, char *argv[]) { Derived obj; // ??? return 0; }; In diesem Fall dürften Compiler und Linker keine Probleme haben, das Programm erfolgreich zu bauen. Führen wir das Programm allerdings anschließend aus, so werden wir sofort mit einem Fehler "pure virtual function called" (o.ä., je nach Compiler) konfrontiert. Dasselbe passiert, wenn wir den indirekten Aufruf der virtuellen Methode via Dieses Beispiel zeigt: eine Ursache für diesen Fehler in Programmen können also indirekte Aufrufe virtueller Funktionen aus Konstruktoren oder Destruktoren sein, die man unbedingt vermeiden sollte! „Never call virtual functions during construction or destruction.“ Baumelnde ZeigerEs existiert eine weitere, potenzielle Möglichkeit, mit einem Fehler "pure virtual function called" konfrontiert zu werden, und das ist - wie so häufig - der sorglose Umgang mit Zeigern (Pointer). Um dieses zu demonstrieren, müssen wir unsere beiden Klassen aus dem vorherigen Beispiel wieder ein klein wenig modifizieren und mit ein paar Ausgaben auf stdout dekorieren: #include <string>
#include <iostream> class Base { protected: Base(void) { std::cout << "Base c'tor called." << std::endl; }; public: virtual ~Base(void) { }; void doSomething(void) { std::cout << "Base::doSomething called." << std::endl; thePureVirtual(); }; protected: virtual bool thePureVirtual(void) const = 0; }; class Derived : public Base { public: Derived(void) { std::cout << "Derived c'tor called." << std::endl; }; virtual ~Derived(void) { std::cout << "Derived d'tor called." << std::endl; }; protected: virtual bool thePureVirtual(void) const { std::cout << "Derived::thePureVirtual called." << std::endl; return true; }; }; Auch die main-Funktion wird ein wenig verändert - wir machen ganz bewusst ein fehlerhaftes Programm daraus:
Bis zu Zeile 6 ist noch alles in Ordnung. Schaut man sich die Pointer in einem Debugger an, so zeigen beide nach der Zuweisung auf dasselbe, gültige Objekt, welches seinerseits eine virtuelle Funktionstabelle (englisch: virtual function table, kurz auch vtbl) besitzt. In dieser virtuellen Funktionstabelle, die nichts anderes ist als eine Tabelle von Methodenzeigern, sieht man dann sowohl den virtuellen Destruktor, als auch die rein virtuelle Funktion Das Problem entsteht mit dem Zerstören von Wie es nun an der Adresse im Speicher aussieht, auf die p2 noch immer zeigt, kann nicht gesagt werden. Auch was nun in Zeile 9 passiert, wo der Zeiger dereferenziert und die Methode Es kann aber auch sein, das der Speicherbereich noch immer in exakt denselben Zustand ist wie vor dem Tipps zu FehlersucheDer Aufruf einer rein virtuellen Funktion ist ein Programmierfehler! Daher kommt man nicht umhin, den Aufruf der rein virtuellen Funktion zu suchen und den Code dort so zu schreiben, dass sie nicht mehr aufgerufen wird. Eine Möglichkeit den unzulässigen Aufruf zu finden, ist, rein virtuelle Funktionen vorübergehend durch virtuelle Funktionen mit einer Implementierung zu ersetzen, welche das Programm kontrolliert anhält und Zugriff auf den call stack (Aufrufreihenfolge) gewährt. Unter Windows bzw. Visual C++ kann man dazu beispielsweise die Windows API-Funktion #include <Windows.h>
class Base { // ... protected: virtual bool thePureVirtual(void) const { DebugBreak(); return false; }; // ... }; Eine weitere Möglichkeit im Debugger an den call stack zu kommen, ist das Setzen eines Haltepunkts (break point) in der Funktion |



![Validate my RSS feed [Valid RSS]](/images/stories/valid-rss-rogers.png)




