2009-05-08 5 views
2

Nous avons une classe dont le comportement sémantique est comme ce qui suit: -Destructeurs de lancer, corruption de mémoire?

struct Sample 
{ 
    ~Sample() throw() 
    { 
    throw 0; 
    } 
}; 

void f() 
{ 
    try 
    { 
    delete new Sample; 
    } 
    catch (...){ 
    } 
} 

Je sais que jeter des exceptions dans dtors est le mal; mais l'abandon d'une ressource de bibliothèque de tierce partie jette une exception (mais peut être ré-acquis immédiatement, quelque chose d'étrange!). Il existe également un pool de cette ressource, par exemple un tableau/conteneur de classe Sample. Donc, il y a deux cas à considérer: la destruction d'un objet alloué dynamiquement et la destruction d'un tableau d'objets alloués dynamiquement.

Actuellement, l'application se bloque aléatoirement à différents points d'exécution uniquement lorsque la version de tableau (pool) est utilisée. Nous pensons que cela est dû à une corruption de la mémoire, mais pourquoi la version non-poolée fonctionne-t-elle? Qu'arrive-t-il à la mémoire allouée?

Est-ce un comportement indéfini? Que se passe-t-il dans le cas d'un tableau? Est-ce que les dtors (atleast, not memory) de tous les éléments d'un tableau (disons si le dteur du premier élément est lancé) sont appelés?

Merci à l'avance,

EDIT-1: Eh bien, nous avons suivi le bas à dtors de certains éléments-tableau ne sont pas appelés. Mais la mémoire allouée ne semble pas avoir des problèmes ... est la section 5.3.5.7 Après de SC22-N-4411.pdf)

If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will 
call a deallocation function (3.7.4.2). Otherwise, it is unspecified whether the deallocation function will be 
called. [ Note: The deallocation function is called regardless of whether the destructor for the object or some 
element of the array throws an exception. —end note ] 

< \ snip>

On dirait la mémoire est toujours désaffecté dans de tels cas. Ai-je raison d'interpréter la norme?

Répondre

5

Il y a deux choses qui pourraient se produire dans cette situation:

  • fin() est appelée
  • non définie comportement

Dans aucun des cas, la mémoire allouée dynamiquement ne peut être libérée (sauf que la terminaison de l'application renvoie nosces à l'OS).

4

C++ met fin à votre application si un agent lève une exception pendant le déroulement de la pile en raison d'une autre exception.

Comme il est pratiquement impossible de déterminer dans quelles circonstances un détecteur est appelé, la règle standard est jamais de jeter des exceptions de dtors. Si votre 3rd Party Library émet une exception, attrapez-la dans votre dtor, connectez-la ou enregistrez son état dans un cache statique où vous pouvez la récupérer "plus tard", mais ne lui permettez pas de sortir de votre dtor.

Pour ce faire, vérifiez si votre collection d'objets fonctionne, cela pourrait provoquer vos plantages.

MISE À JOUR

Malheureusement, je ne suis pas un avocat de spécification, préférant l'approche Fisherman's Friend de « sucer un voir ».

J'écrirais une petite application avec une classe qui alloue un méga hors du tas. Dans une boucle, créez un tableau des classes, demandez aux classes dtor de lancer une exception et lancez une exception à la fin de la boucle (en provoquant la décompression de la pile et appelez les dtors du tableau des classes) et regardez-la pour voir votre utilisation de VM passer à travers le toit (dont je suis sûr que ce sera le cas).

Lancer une exception destructor est mauvais

Désolé je ne peux pas vous donner le chapitre et le verset, mais c'est ma « croyance »

+0

Actuellement, je ne souhaite pas modifier la classe Sample. Je suis au courant de stt :: terminate() etc. Ayant une spécification throw(). car dtor est également inutile car il n'empêche aucune exception de s'échapper ni n'empêche quiconque de lancer. Ma question est plus inclinée vers «Qu'est-ce qui arrive à la mémoire? Est-il libéré? Est-ce que c'est une fuite? Ou est-ce un comportement indéfini? La citation des sections standard C++ vous aidera. – Abhay

+0

O.K. La norme C++ est assez laconique pour moi aussi. La seule raison pour laquelle je demande est; si je suis d'interdire jeter/échapper exception de dtors dans mon projet actuel, je aurais besoin de l'aide d'un «avocat de spécifications» :-) – Abhay

1

1) parce que si exception est en cours de traitement et une autre exception se produit l'application quittera.Ainsi, si, pendant la gestion des exceptions, votre application efface des objets (par exemple, un destructeur d'appels sur chacun d'eux) et qu'un des destructeurs lance une autre exception, l'application va quitter.

2) Je ne pense pas que les destructeurs soient appelés automatiquement pour le reste des éléments du conteneur lorsque l'un d'entre eux émet une exception. Si l'exception est levée dans le destructeur du conteneur, le reste des éléments ne sera définitivement pas nettoyé car l'application déroulera la pile tout en gérant l'exception.

La méthode standard d'écriture destructor devrait être quelque chose comme:

A::~A() 
{ 
    try { 
     // some cleanup code 
    } 
    catch (...) {} // Too bad we will never know something went wrong but application will not crash 
} 
+0

1) Bien. Bien connu. Mais je n'ai pas l'intention de modifier la classe Sample. 2) Pouvez-vous citer des sections standard C++ qui traitent de la suppression de tableau lorsqu'une exception est levée par dtor de l'un de ses éléments. Merci – Abhay

1

Destructeurs ne doivent jamais jeter des exceptions, il conduit à un comportement non défini et pourrait également conduire à des fuites de mémoire. Considérons l'exemple suivant

T* p = new T[10]; 
delete[] p; 

Alors, comment sera nouveau [] et supprimer [] réagir si T jette un destructor? Considérons tout d'abord que les constructions se sont déroulées sans problème, puis lors de la suppression du quatrième destructeur ou du même destructeur. delete [] peut choisir de propager l'exception qui entraînerait la perte de tous les autres objets T laissés dans la matrice, non récupérables et donc non destructibles. Il ne peut pas non plus "attraper" l'exception car la suppression ne serait plus neutre sur les exceptions.

Ensuite, disons qu'un des lanceurs de constructeurs. Supposons que le 6ème constructeur lève une exception. Pendant le déroulement de la pile, tous les objets construits jusqu'à présent doivent être déconstruits. Donc le 5ème, 4ème, 3ème et ainsi de suite est appelé destructeur. Que se passe-t-il si le 4ème ou 3ème destructeur génère une autre exception? Devrait-il être absordé ou propagé?

Il n'y a pas de réponse à cela, donc cette rubrique mène à un comportement indéfini.

Et comme il est indiqué dans mon premier exemple, pourrait également conduire à des fuites de mémoire ..

+1

"Que se passe-t-il si le 4ème ou 3ème destructeur lance une autre exception?" terminate() est appelé (15.5.1). –

2

Depuis que vous avez demandé dans un commentaire pour le chapitre et le verset:

15,2: 3 a une note, en disant:

« Si un destructor appelé lors de sorties de déroulement de la pile avec une exception fin est appelée (15,5. 1) Donc, les destructeurs doivent généralement attraper des exceptions et ne pas les laisser se propager hors du destructeur "

Pour autant que je puisse en juger, la seule justification pour dire" généralement "là-bas, c'est qu'il est possible d'écrire très soigneusement un programme de sorte qu'aucun objet dont le destructeur peut lancer, n'est jamais supprimé dans le cadre du déroulement de la pile. Mais c'est une condition plus difficile à appliquer dans le projet moyen, que "les destructeurs ne doivent pas jeter".

15.5.1 et 2 disent:

"Dans les situations suivantes ... - lorsque la destruction d'un objet pendant le déroulement pile (15,2) sort en utilisant une exception ... void terminate() est appelé".

Il y a d'autres conditions pour terminate() dans 15.5.1, qui suggèrent d'autres choses que vous pourriez ne pas vouloir lancer: copier des constructeurs d'exceptions, des gestionnaires atexit, et unexpected. Mais par exemple, la raison la plus probable de l'échec d'un constructeur de copie est la mémoire insuffisante, qui, par ex. Linux pourrait segfault au lieu de lancer une exception de toute façon. Dans de telles situations, terminate() ne semble pas si mauvais.

On dirait que la mémoire est toujours désallouée dans de tels cas. Ai-je raison d'interpréter la norme?

Il me semble que la mémoire de l'objet en cours de suppression est toujours désallouée. Il ne s'ensuit pas que toute mémoire qui lui appartient via des pointeurs, et libère dans son destructeur, est désallouée, surtout s'il s'agit d'un tableau et qu'il y a donc plusieurs destructeurs à appeler.

Oh oui, et avez-vous confiance en la sécurité de votre bibliothèque tierce? Est-il possible que l'exception pendant la gratuité quitte la bibliothèque dans un état que ses auteurs n'ont pas anticipé, et que l'accident soit à cause de cela?

+0

Merci ou vos entrées. La classe Sample est un wrapper pour une classe de bibliothèque thrid-party, qui est gelée pour le moment. Nous l'avons donc enveloppé de nouveau, dans lequel notre agent veille à ce que les exceptions n'échappent pas. Mais le noeud du problème était; certains decteurs du tableau n'ont pas été appelés en raison d'une exception d'un dteur d'éléments antérieurs. Enfait, j'ai découvert à partir du C++ Std. que les decteurs de matrice sont appelés élément-à-premier (mémoire décroissante) par élément. Mais l'objet qui possède ce tableau a été désalloué malgré l'exception de l'un de ses membres de tableau. Cela conduit à une corruption de mémoire sérieuse et des plantages aléatoires. – Abhay

Questions connexes