2013-02-22 8 views
6

Im folgenden Code aufgerufen wird, wenn ptr gelöscht wird, wird der Destruktor für Base genannt, aber nicht den destructor für Derived (wegen destructor von Base nicht virtuell sind).Zerstörung der Klassenmitglieder, wenn destructor nicht

class Base 
{ 
    int b; 
}; 

class Derived : public Base 
{ 
    int d; 
}; 

int main(void) 
{ 
    Base * ptr = new Derived(); 
    delete ptr; 
    return 0; 
} 

Valgrind berichtet, dass das Programm keine Speicherlecks enthält, die ich in dem Sinne wahr Vermutung ist, dass alle newed Daten in diesem speziellen Fall gelöscht. Meine Frage ist - vorausgesetzt, dass der (Standard) Destruktor von Derived nicht aufgerufen wird, wann und wie ist der Speicher für d freigegeben oder zurückgefordert?

+3

Ich glaube, der Speicher wird aufgrund 'delete ptr' zurückgefordert, es wird nur kein Destruktor aufgerufen, was ein * potentielles * undefiniertes Verhalten sein könnte. – iammilind

+5

Es ist nicht möglich. Es ist undefiniertes Verhalten. (§5.3.5/3) –

+2

Ihr Valgrind-Aufruf sagt "keine Speicherlecks", mein 'MemoryLogger' sagt" zugewiesene 8 Bytes bei 0x3b2380; Freigabe von 4 Bytes bei 0x3b2380; Fehler: 4 Bytes sind noch belegt! '. UB. Möglicherweise könnte dies auch sagen: "Einen schönen Tag, Sir". – Zeta

Antwort

8

zurückgewonnen werden undefiniert Verhalten delete auf einer Basisklasse Zeiger auf eine abgeleitete Klasse Objekt zu nennen, wenn der Destruktor in der Basisklasse ist nicht virtual.

Undefiniertes Verhalten bedeutet, dass alles passieren kann. Unabhängig davon, ob Speicherverluste auftreten oder nicht, spielt Ihr Programm keine Rolle, und Sie können sich nicht auf ein angezeigtes Verhalten verlassen.

Damit Ihr Code gültig ist, muss der Destruktor in der Basisklasse virtual lauten.


C++ 11 Standard 5.3.5 Löschen:
Para 3:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

1

Wie andere große Jungs erwähnt, sagt der Standard es nicht definiertes Verhalten ist, aber in der Regel unter Compiler, die dies bis zu System allocator Implementierung über den Wettbewerb und Nutzer kümmern, die sich von Plattform zu Plattform verschieden sein können. Der Destruktor selbst befreit nicht den Speicher des Objekts Derived, das Felder enthält. Wenn Sie new Derived aufrufen, wird die globale Implementierung von operator new aufgerufen und empfängt die Anzahl der zuzuweisenden Bytes Es wird sizeof(Derived) in Ihrem Fall sein.

Nach tatsächlichem Freigeben des Speichers (global operator delete) weiß der Zuordner, wie viele Bytes von der Adresse des Objekts freigegeben werden sollen, das freigegeben wird. So verwendet der Speicher sowohl von b und d wird

+0

Nein, Sie können nicht darauf zählen, dass der Speicher freigegeben wird. Die Kommentare sind korrekt, dies ist ein nicht definiertes Verhalten. Sie können nicht vorhersagen, was dann passieren wird. – MSalters

+0

@MSalters Ja, Sie haben Recht. Ich beschrieb, wie es früher für mich funktionierte - wusste nicht, dass es nur eine Annehmlichkeit ist. –

+0

@MSalters: Roman versuchte nur zu erklären, was höchstwahrscheinlich in diesem speziellen Fall mit diesem bestimmten Compiler passiert ist und warum Valgrind keinen Speicher gefunden hat Leck. Er hat nicht gesagt, dass es nicht UB ist. –

2

die falsche destructor aufrufen, glaube ich, ist nicht definiertes Verhalten. Sie sollten einen virtuellen Destruktor haben.

Da die Zuweisung in einem Block für die abgeleitete Klasse erfolgt, "funktioniert", weil die Interna von delete einfach die Größe der Zuweisung verfolgen und somit die 8 Bytes freigibt, die das Objekt belegt, anstelle der vier, die Base verwenden würde.

Beachten Sie, dass der virtuelle Destruktor nicht selbst dafür verantwortlich ist, den Speicher freizugeben, auf den this zeigt. Das passiert in delete nachdem der Destruktor aufgerufen wurde.

+0

Es mag gut sein, dass diese bestimmte Implementierung von delete die Größe der Zuweisung verfolgt, aber das ist nicht erforderlich und die Tatsache, dass es zu funktionieren scheint nur Zufall. Insbesondere weisen Speichermanager häufig eine minimale Blockgröße auf, die sie zuweisen werden, und es könnte sein, dass der Block, der für ein "Abgeleitetes" Objekt zugewiesen ist, die gleiche Größe wie der für ein "Basis" -Objekt zugewiesene Block aufweist. Auf jeden Fall ist undefiniertes Verhalten einfach undefiniert; darüber zu spekulieren, warum es in einem bestimmten Fall nicht möglich sein könnte, Symptome zu zeigen, ist in der Regel nur zu raten. –

+0

Oh, und während ich auf dem Thema bin, wird die Freigabe des Speichers oft im Destruktor gemacht; es erhält eine geheime Flagge, die ihm sagt, ob er auch den Speicher löschen soll. Das macht es einfacher, mit dem klassenspezifischen 'operator delete' und unterschiedlichen Größen abgeleiteter Typen umzugehen. Entschuldigung wegen der Vorlesung ... –

Verwandte Themen