2010-01-15 5 views
6

Ich bin gerade dabei, ein Crashlog zu debuggen. Der Absturz tritt auf, weil der vtable-Zeiger eines (C++ -) -Objekts 0x1 ist, während der Rest des Objekts in Ordnung zu sein scheint, soweit ich es aus dem Absturzprotokoll ersehen kann.Unter welchen Umständen kann ein Vtable-Zeiger Null (oder 0x1) sein?

Das Programm stürzt ab, wenn es versucht, eine virtuelle Methode aufzurufen.

Meine Frage: Unter welchen Umständen kann ein Vtable-Zeiger Null werden? Löscht der Operator delete den vtable-Zeiger auf null?

Dies tritt auf OS X mit gcc 4.0.1 (Apple Inc. build 5493).

+0

Können Sie klären, ob der Funktionszeiger in der VTable 0 ist oder wenn es die diese Zeiger, die null ist? Oder ist es ein Zeiger im Objekt, der auf die aktuelle vtable verweist? –

+0

Der Vtable-Zeiger ist 1. Der Absturz tritt auf, wenn versucht wird, 'call * 0x1c (% eax)' (in AT & T-Syntax) und der Wert von EAX ist 1 (nicht Null, wie ich falsch angegeben). – Tobias

+0

Führen Sie es in Gdb, legen Sie einen Überwachungspunkt an die Adresse, sehen Sie, was es schreibt. –

Antwort

7

Könnte ein Gedächtnis trampeln - etwas, das versehentlich über das 10 schreibt. Es gibt eine nahezu unendliche Anzahl von Möglichkeiten, dies in C++ zu erreichen. Ein Pufferüberlauf zum Beispiel.

7

Jede Art von undefined Verhalten Sie haben möglicherweise zu dieser Situation führen. Zum Beispiel:

  • Fehler in Zeigerarithmetik oder anderen, die Ihr Programm in ungültigen Speicher schreiben.
  • Nicht initialisierte Variablen, ungültige Umwandlungen ...
  • Die polymorphe Behandlung eines Arrays kann dies als sekundären Effekt verursachen.
  • Versuch, ein Objekt nach dem Löschen zu verwenden.

Siehe auch die Fragen What’s the worst example of undefined behaviour actually possible? und What are all the common undefined behaviour that a C++ programmer should know about?.

Ihre beste Wette ist die Verwendung eines Begrenzungs- und Speicherprüfprogramms als Hilfe für schweres Debugging.

1

Dies ist vollständig abhängig von der Implementierung. Es wäre jedoch ziemlich sicher anzunehmen, dass nach dem Löschen eine andere Operation den Speicherplatz auf null setzen kann.

Andere Möglichkeiten sind Überschreiben des Speichers durch einen losen Zeiger - eigentlich in meinem Fall ist es fast immer so ...

Das hieß, sollen Sie nie ein Objekt nach dem Löschen zu verwenden versuchen.

+1

löschen kann sicherlich tun, aber ich bin mir nicht bewusst, eine Implementierung, wo es tut –

+0

@Neil: Implementierungen sind nicht mein Feld. Ich änderte die Antwort zu etwas "mehr wahr" ... –

3

Meine erste Schätzung wäre, dass ein Code memset() ein Klassenobjekt ist.

5

Ein sehr häufiger Fall: Der Versuch, eine rein virtuelle Methode aus dem Konstruktor aufzurufen ...

Konstrukteurs

struct Interface 
{ 
    Interface(); 
    virtual void logInit() const = 0; 
}; 

struct Concrete: Interface() 
{ 
    virtual void logInit() const { std::cout << "Concrete" << std::endl; } 
}; 

Nun nehmen wir die folgende Implementierung von Interface()

Interface::Interface() {} 

Dann ist alles in Ordnung:

Concrete myConcrete; 
myConcrete.pure(); // outputs "Concrete" 

Es ist so ein Schmerz rein nach dem Konstruktor zu nennen, wäre es besser, den Code richtig zu faktorisieren?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;) 

Dann können wir es in einer Linie tun !!

Concrete myConcrete; // CRASHES VIOLENTLY 

Warum?

Da das Objekt von unten nach oben gebaut wird. Lass es uns anschauen.

Anleitung eine Concrete Klasse (grob)

  1. Weisen Sie ausreichend Speicher (natürlich), und genug Speicher für die _vtable zu (1 Funktionszeiger pro virtuelle Funktion, in der Regel in der Reihenfolge bauen sie deklariert sind von links nach rechts Basis ausgehend)

  2. Anruf Concrete Konstruktor (der Code, den Sie nicht sehen)

    a>Interface Konstruktoraufruf, die die _vtabl initialisieren e mit seinem Zeiger

    b>Interface Konstruktor Körper Anruf (Sie schrieb, dass)

    c> die Zeiger in der _vtable Überschreibung Beton außer Kraft setzen für diesen Methoden Concrete Konstruktor Körper

    d> Anrufe (Sie schrieb, dass)

Also, was ist das Problem? Nun, schauen Sie sich b> und c> bestellen;)

Wenn Sie eine virtual Methode aus einem Konstruktor aufrufen, tut es nicht, was Sie erhoffen. Es geht an die _vtable, um den Zeiger nachzuschlagen, aber die _vtable ist noch nicht vollständig initialisiert. Also, für alles, was zählt, ist die Wirkung von:

D() { this->call(); } 

ist in der Tat:

D() { this->D::call(); } 

Wenn eine virtuelle Methode aus einem Konstruktor aufrufen, müssen Sie nicht die volle dynamische Art des Objekts Wenn Sie einen Build erstellen, wird der statische Typ des aktuellen Konstruktors aufgerufen.

In meinem Interface/Concrete Beispiel bedeutet es Interface Typ, und das Verfahren ist virtuell rein, so wird der _vtable keinen echten Zeiger (0x0 oder 0x01 zum Beispiel halten, wenn Ihr Compiler ist freundlich genug, um Setup-Debug-Werte helfe dir da).

Destructors

Coincidently, lassen Sie uns den Destructor Fall untersuchen;)

struct Interface { ~Interface(); virtual void logClose() const = 0; } 
Interface::~Interface() { this->logClose(); } 

struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; } 

Concrete::~Concrete() { delete[] m_data; } // It's all about being clean 
void Concrete::logClose() 
{ 
    std::cout << "Concrete refering to " << m_data << std::endl; 
} 

Also, was auf die Zerstörung geschieht? Nun, die _vtable funktioniert gut, und der echte Laufzeittyp wird aufgerufen ... Was es hier bedeutet ist jedoch undefiniertes Verhalten, denn wer weiß, was mit m_data passiert ist, nachdem es gelöscht wurde und bevor Interface destructor aufgerufen wurde?Ich weiß nicht;)

Fazit

Nie virtuelle Methoden aus Konstrukteuren oder Destruktoren aufrufen.

Wenn es nicht das ist, sind Sie mit einem Speicher Korruption links, Pech;)

+0

Vielen Dank für Ihre ausführliche Antwort. In meinem Fall ist das leider nicht der Fall. – Tobias

Verwandte Themen