Question

Nous avons une classe dont le comportement sémantique est le suivant: -

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

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

Je sais que jeter des exceptions dans les doigts est un mal; mais l'abandon d'une ressource de bibliothèque tierce est 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 la classe Sample. Il y a donc 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 de manière aléatoire à différents points d'exécution uniquement lorsque la version du groupe (pool) est utilisée. Nous pensons que cela est dû à une corruption de mémoire, mais pourquoi alors la version non groupée fonctionne-t-elle?.

Qu'advient-il de la mémoire allouée? Est-ce un comportement indéfini? Que se passe-t-il dans le cas d'un tableau? Les noms (au moins, pas la mémoire) de tous les éléments d’un tableau (par exemple, si le nom du premier élément est lancé) sont appelés?

Merci d'avance,

EDIT-1: Eh bien, nous avons dépisté jusqu’à dtors des éléments de tableau non appelés. Mais la mémoire allouée ne semble pas avoir de problèmes ... Vous trouverez ci-dessous la section 5.3.5.7 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 >

Dans de tels cas, la mémoire est toujours désallouée. Ai-je raison d'interpréter la norme?

Était-ce utile?

La solution

Il y a deux choses qui pourraient arriver dans cette situation:

  • terminate () s'appelle
  • comportement non défini

Dans les deux cas, la libération de la mémoire allouée dynamiquement peut être garantie (sauf que la fermeture de l'application renvoie bien sûr toutes les ressources au système d'exploitation).

Autres conseils

C ++ mettra fin à votre application si un serveur génère une exception alors que la pile est en train d'être déroulée à cause d'une autre exception.

Comme il est pratiquement impossible de déterminer dans quelles circonstances un dénommeur est appelé, la règle standard consiste à ne jamais renvoyer des exceptions de détors.

Si votre bibliothèque tierce génère une exception, saisissez-la dans votre dtor, enregistrez-la ou enregistrez son état dans un cache statique où vous pourrez la récupérer & "plus tard &"; ne lui permettez pas de sortir de votre dtor.

Faites ceci, puis voyez si votre collection d'objets fonctionne, cela pourrait causer des plantages.

MISE À JOUR

Malheureusement, je ne suis pas un juriste spécialisé dans le domaine de la technologie, je préfère l'approche de l'ami du pêcheur de & "sucer et voir &";

J'écrirais une petite application avec une classe qui alloue un méga-octet. Dans une boucle, créez un tableau des classes, demandez aux classes dtor de lever une exception, et de capturer une exception à la fin de la boucle (provoquant le déroulement de la pile et l'appel des compteurs du tableau de classes) et observez-le. pour voir votre utilisation de la VM aller à travers le toit (ce qui, j'en suis sûr, va le faire).

Désolé, je ne peux pas vous donner le chapitre et le verset, mais c'est ma & "conviction &";

.

Depuis que vous avez demandé dans un commentaire de chapitre et de vers:

15.2: 3 a une note disant:

& "Si un destructeur appelé lors du déroulement de la pile, les sorties avec une exception se terminent est appelé (15.5.1). Ainsi, les destructeurs devraient généralement intercepter les exceptions et ne pas les laisser se propager hors du destructeur & Quot;

.

Autant que je sache, la seule justification pour dire & "généralement &"; là, c’est qu’il est possible d’écrire très soigneusement un programme afin qu’aucun objet dont le destructeur puisse lancer, ne soit jamais supprimé lors du déroulement de la pile. Mais c’est une condition plus difficile à appliquer dans un projet moyen que & "; Les destructeurs ne doivent pas lancer &";.

.

15.5.1 et 2 disent:

& "; Dans les situations suivantes ... - lorsque la destruction d'un objet lors du déroulement de la pile (15.2) se termine avec une exception ... void terminate() est appelé &";

Il existe d'autres conditions pour terminer () dans 15.5.1, qui suggèrent d'autres choses que vous ne voudrez peut-être pas ignorer: copier les constructeurs d'exceptions, les gestionnaires atexit et unexpected. Mais par exemple, la raison la plus probable pour laquelle un constructeur de copie échoue est le manque de mémoire, ce qui par exemple linux pourrait segfault au lieu de lancer une exception de toute façon. Dans de telles situations, terminate () ne semble pas si mal.

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 supprimé est toujours désallouée. Il ne s'ensuit pas que la mémoire qu'elle possède via des pointeurs et libère dans son destructeur est libérée, en particulier s'il s'agit d'un tableau et qu'il y a donc plusieurs destructeurs à appeler.

Oh oui, et croyez-vous que votre bibliothèque tierce est protégée contre les exceptions? Est-il possible que l'exception pendant Free libère la bibliothèque dans un état que ses auteurs n'avaient pas anticipé, et que le crash en soit la cause?

1) Le lancement d'une exception à partir de destructor est incorrect, car si une exception est en cours de traitement et qu'une autre exception se produit, l'application se ferme. Ainsi, si lors de la gestion des exceptions, votre application efface les objets (par exemple, appelle destructor sur chacun d’eux) et que l’un des destructeurs lève une autre exception, l’application se ferme.

2) Je ne pense pas que les destructeurs soient appelés automatiquement pour le reste des éléments du conteneur lorsque l'un d'eux lève 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 pendant le traitement de l’exception.

La manière habituelle d’écrire destructor devrait ressembler à ceci:

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

Les destructeurs ne doivent jamais lancer d'exceptions, cela conduit à un comportement indéfini et peut également conduire à des fuites de mémoire. Considérons l'exemple suivant

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

Alors, comment vont réagir new [] et delete [] si T lance un destructeur?

Considérons d’abord que toutes les constructions se sont bien déroulées, puis lors de la suppression [] du quatrième destructeur ou plus. delete [] peut choisir de propager l'exception qui entraînerait la perte de tous les autres objets T laissés dans le tableau, non récupérables et donc indestroyables. Il ne peut pas non plus & Quot; attraper & "; l'exception car alors supprimer ne serait plus neutre comme exception.

Deuxièmement, dites l'un des constructeurs jette. Supposons que le 6ème constructeur lève une exception. Lors du déroulement de la pile, tous les objets construits jusqu’à présent doivent être déconstruits. Ainsi, les 5ème, 4ème, 3ème et ainsi de suite destructeurs sont appelés. Que se passe-t-il si le 4ème ou le 3ème destructeur lève une autre exception? Devrait-ce être absordé ou propagé?

Il n'y a pas de réponse à cette question, ce sujet entraîne donc un comportement indéfini.

Et comme indiqué dans mon premier exemple, cela pourrait également entraîner des fuites de mémoire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top