2010-02-10 9 views
3
#include <iostream> 
using namespace std; 

class B 
{ 
public: 
    B() { cout << "Base B()" << endl; } 
    ~B() { cout << "Base ~B()" << endl; } 
private: 
    int x; 
}; 

class D : public B 
{ 
public: 
    D() { cout << "Derived D()" << endl; } 
    virtual ~D() { cout << "Derived ~D()" << endl; } 
}; 

int 
main (void) 
{ 
    B* b = new D; 
    delete b; 
} 


---- output---------- 
Base B() 
Derived D() 
Base ~B() 
*** glibc detected *** ./a.out: free(): invalid pointer: 0x0930500c *** 
======= Backtrace: ========= 
/lib/tls/i686/cmov/libc.so.6[0xb7d41604] 
/lib/tls/i686/cmov/libc.so.6(cfree+0x96)[0xb7d435b6] 
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0xb7f24231] 
./a.out[0x8048948] 
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7ce8775] 
./a.out[0x80487c1] 
Aborted 

Si je supprimer le membre privé « int x » de la classe de base, il fonctionne très bienC++: morceau suivant d'accidents Code

Répondre

2

Ce que vous faites est UB, mais pour le compilateur spécifique que vous utilisez le comportement peut être décrit comme suit. pour faciliter les ASCII-graphiques ci-dessous, en modifiant l'exemple, l'ajout d'un membre y-D et modifiant main

#include <iostream> 
using namespace std; 

class B 
{ 
public: 
    B() { cout << "Base B()" << endl; } 
    ~B() { cout << "Base ~B()" << endl; } 
private: 
    int x; 
}; 

class D : public B 
{ 
public: 
    D() { cout << "Derived D()" << endl; } 
    virtual ~D() { cout << "Derived ~D()" << endl; } 
private: 
    int y; // added. 
}; 

int 
main (void) 
{ 
    D* d = new D; // modified. 
    B* b = d; 
    delete b; 
} 

Dans le compilateur que vous utilisez, vtable, le cas échéant, est placé au début du bloc de mémoire. Dans le compilateur que vous utilisez la mise en page de mémoire pour cela est la suivante:

+--------+ 
| vtable | <--- d points here, at the start of the memory block. 
+--------+ 
| x  | <--- b points here, in the middle of the memory block. 
+--------+ 
| y  | 
+--------+ 

plus tard lors de l'appel delete b le programme va essayer de free le bloc de mémoire à l'aide du pointeur b, qui pointe vers le milieu du bloc de mémoire .

Cela entraînera à son tour l'accident en raison de l'erreur de pointeur non valide.

+0

Le problème est 'd' n'a pas de vtable. Donc, les pointeurs 'b' au début de 'd' et il ne devrait pas y avoir de problèmes. Je pense qu'il y a quelque chose de plus dans la mémoire (ajouté par le compilateur pour une raison quelconque) causant le crash. –

6

class B ne dispose pas d'un destructor virtuel et vous essayez de delete une instance de class D dérivé de class B via un pointeur sur class B - ce comportement est indéfini. Vous devez rendre le destructeur virtuel class B pour que votre code fonctionne.

12

Le destructeur de la classe de base B doit également être virtuel.

+0

Je ne veux pas que –

+4

@Ganesh Kundapur: Vous ne voulez pas qu'il se plante soit ... – dalle

+1

Malheureusement, vous devez, si vous voulez libérer l'objet comme vous le faites dans main(). Sinon, vous devez le supprimer en tant qu'objet D: D * d = (D *) b; supprimer d; –

2

Une réponse alternative peut être d'utiliser boost :: shared_ptr: son constructors se souviendront templated que votre objet est de type D.

#include <boost/shared_ptr.hpp> 

int 
main (void) 
{ 
    boost::shared_ptr<B> b(new D); 
} 

La modification ci-dessus de votre code fonctionne très bien, même sans virtuel destructeur. Par ailleurs, à moins que vous ne souhaitiez stocker des pointeurs sur D, il est inutile de rendre D destructeur virtuel.

+0

sizeof (B) = 4 octets alors que sizeof (D) = 8 octets. Tout en libérant la partie de base de l'instance dérivée, tente de libérer 8 octets, alors qu'il a 4 octets alloués. Ai-je raison? –

+0

@Ganesh Kundapur: Le problème n'est pas le mauvais nombre d'octets libérés, le problème est que l'implémentation n'est pas obligée de faire quelque chose de raisonnable dans le cas que vous décrivez dans la question. C'est ce qu'on appelle UB - peut-être que cela fonctionne, peut-être qu'il se bloque, peut-être qu'il provoque un bug dévastateur retardé. – sharptooth

+0

Pourquoi est-ce que quelqu'un -1 ma réponse? –

1

Eh bien, si vous ne voulez pas un destructeur virtuel, vous doit supprimer l'objet avec un pointeur vers son type réel:

int 
main (void) 
{ 
    D* as_d = new D; 
    B *as_b = as_d; 
    // you can use the object via either as_b or as_d but 
    // you must delete it via as_d 
    delete as_d; 
} 

Cela dit, si vous ne faites pas attention, il peut être facile de supprimer l'objet à travers le mauvais pointeur.

Donc, je sais que vous ne le voulez pas, mais pour votre propre santé mentale, il suffit de rendre le B destructeur virtuel.

0

Lorsque vous détruisons tiriez objet class pointeur de la classe de base puis, cela conduira à la destruction partielle de l'objet ((seul constructeur de classe de base est invoquée) si la classe de base cteur n'est pas virtuelle.

vous devez donc faire classe de base desrtuctor comme virtuelle.

+0

Il n'y a rien à détruire ici - la classe B a une variable membre entier avec un destructeur trivial, la classe D n'en a aucune, rien à détruire. Le problème est que son code déclenche un comportement indéfini. Spéculer sur la destruction partielle n'a aucun sens ici. – sharptooth

0

Lorsqu'une classe est purement non virtuelle, elle n'a pas d'entrée pour le VPTR. Donc B est exactement 4 octets. Il s'agit d'un illustration de la mémoire. VPTR est au plus petit emplacement de mémoire.C'est ainsi que toutes les classes dérivées savent où trouver le VPTR. Par conséquent, D est 8 octets, le premier 4 est VPTR, et les 4 suivants pour x.

Mais, n'est-ce pas D is-a B? Non, cela fonctionne de la même manière que l'héritage multiple. Lorsque vous attribuez une adresse D dans un pointeur B, le compilateur le sait, et au lieu de vous donner l'adresse REAL de D, il vous donne l'adresse décalée pour qu'elle fonctionne comme un B. Dans ce cas, c'est vraiment offset par 4 octets. Donc, quand vous essayez de B-> x, vous obtenez la bonne valeur.

Lorsque vous transmettez cette adresse de décalage au tas en libre, tout devient fou, car ce dont il a besoin est l'adresse d'origine. Ce comportement est non indéfini. Cela arrive aussi dans l'héritage multiple.

+0

Je pense que vtables de l'application sont gérés dans une autre section spéciale de la mémoire, et non dans le tas, donc, ils n'influencent pas le contenu des objets dans le tas. –

0

Je pense que le fait d'être le membre entier provoquant un crash de mémoire, c'est juste une question de "chance". Je veux dire, si votre destructeur dans la classe de base n'est pas virtuel, la "destruction" de D n'est pas appelée.

Ainsi, en mémoire, votre objet D dans les tas pourrait ressembler à:

Object D 
+-----------+ 
| B subobj. | 
+-----------+ 
| D members | 
+-----------+ 

Si la destructor du B n'est pas virtuel et si vous supprimez une base de pointeur vers B, la partie D n'est pas détruit. Dans votre cas, la partie D a la taille 0, et la partie B a une taille de sizeof(int) (4 octets), et cela rend la situation un peu plus compliquée à "deviner", mais peut-être que le compilateur ajoute des informations supplémentaires pour n'importe quelle raison. vos objets en mémoire. Donc, après la suppression de b, mais avant la fin de l'application, peut-être que l'ajout de code par le compilateur au moment de la sortie cause le crash à cause de la suppression incorrecte de b (réutilisation de cette partie de la mémoire) par exemple ou quelque chose comme ça).

Puisque votre code est très court, vous pouvez inspecter le comportement de votre code avec 'gdb', au niveau de l'assemblage, dans l'intervalle entre la suppression de b et la fin de votre code.