Je commencerai par dire, utilisez des pointeurs intelligents et vous n'aurez jamais à vous en soucier.
Quels sont les problèmes avec le code suivant?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Cela a été déclenché par une réponse et des commentaires à une autre question. Un commentaire de Neil Butterworth a généré quelques votes positifs:
Définir des pointeurs sur NULL après la suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où c'est une bonne chose à faire et des moments où cela ne sert à rien et peut cacher des erreurs.
Il y a beaucoup de circonstances où cela n'aiderait pas. Mais d'après mon expérience, ça ne peut pas faire de mal. Quelqu'un m'éclaire.
c++
pointers
null
dynamic-allocation
Mark Ransom
la source
la source
delete
un pointeur nul, ce qui est une des raisons pour lesquelles la mise à zéro d'un pointeur peut être bonne.Réponses:
Mettre un pointeur à 0 (qui est "nul" en C ++ standard, la définition NULL à partir de C est quelque peu différente) évite les plantages lors de doubles suppressions.
Considérer ce qui suit:
Tandis que:
En d'autres termes, si vous ne définissez pas les pointeurs supprimés sur 0, vous aurez des problèmes si vous effectuez des doubles suppressions. Un argument contre la mise à 0 des pointeurs après la suppression serait que cela masque simplement les bogues de double suppression et les laisse non gérés.
Il est préférable de ne pas avoir de bogues de double suppression, évidemment, mais en fonction de la sémantique de propriété et des cycles de vie des objets, cela peut être difficile à réaliser en pratique. Je préfère un bug de double suppression masqué à UB.
Enfin, une remarque concernant la gestion de l'allocation d'objets, je vous suggère de jeter un œil à la
std::unique_ptr
propriété stricte / singulière,std::shared_ptr
à la propriété partagée ou à une autre implémentation de pointeur intelligent, en fonction de vos besoins.la source
NULL
peut masquer un bogue de double suppression. (Certains pourraient considérer que ce masque est en fait une solution - c'est, mais pas une très bonne car il n'atteint pas la racine du problème.) Mais ne pas le définir sur NULL masque loin (FAR!) Plus problèmes courants d'accès aux données après leur suppression.unique_ptr
, qui fait ce que l'on aauto_ptr
essayé de faire, par une sémantique de déplacement.Définir des pointeurs sur NULL après avoir supprimé ce qu'il a indiqué ne peut certainement pas faire de mal, mais c'est souvent un peu un pansement sur un problème plus fondamental: pourquoi utilisez-vous un pointeur en premier lieu? Je peux voir deux raisons typiques:
std::vector
fonctionne, et cela résout le problème de laisser accidentellement des pointeurs vers la mémoire désallouée. Il n'y a pas de pointeurs.new
peut ne pas être le même que celui quidelete
est appelé. Plusieurs objets peuvent avoir utilisé l'objet simultanément dans l'intervalle. Dans ce cas, un pointeur partagé ou quelque chose de similaire aurait été préférable.Ma règle de base est que si vous laissez des pointeurs dans le code utilisateur, vous le faites mal. Le pointeur ne doit pas être là pour pointer vers les ordures en premier lieu. Pourquoi n'y a-t-il pas un objet qui assume la responsabilité de garantir sa validité? Pourquoi sa portée ne se termine-t-elle pas lorsque l'objet pointé se termine?
la source
new
.J'ai une meilleure pratique encore meilleure: si possible, mettez fin à la portée de la variable!
la source
Je place toujours un pointeur sur
NULL
(maintenantnullptr
) après avoir supprimé le ou les objets vers lesquels il pointe.Cela peut aider à attraper de nombreuses références à la mémoire libérée (en supposant que votre plate-forme soit défaillante sur un deref d'un pointeur nul).
Il n'attrapera pas toutes les références à la mémoire libre si, par exemple, vous avez des copies du pointeur qui traînent. Mais certains valent mieux que rien.
Cela masquera une double suppression, mais je trouve que celles-ci sont beaucoup moins courantes que les accès à la mémoire déjà libérée.
Dans de nombreux cas, le compilateur va l'optimiser. Donc, l'argument selon lequel ce n'est pas nécessaire ne me convainc pas.
Si vous utilisez déjà RAII, il n'y a pas beaucoup de
delete
s dans votre code pour commencer, donc l'argument selon lequel l'affectation supplémentaire cause du désordre ne me convainc pas.Il est souvent pratique, lors du débogage, de voir la valeur nulle plutôt qu'un pointeur périmé.
Si cela vous dérange toujours, utilisez plutôt un pointeur intelligent ou une référence.
J'ai également défini d'autres types de descripteurs de ressources sur la valeur sans ressource lorsque la ressource est libre (qui est généralement uniquement dans le destructeur d'un wrapper RAII écrit pour encapsuler la ressource).
J'ai travaillé sur un grand produit commercial (9 millions de relevés) (principalement en C). À un moment donné, nous avons utilisé la magie des macros pour annuler le pointeur lorsque la mémoire était libérée. Cela a immédiatement exposé de nombreux bugs cachés qui ont été rapidement corrigés. Autant que je me souvienne, nous n'avons jamais eu de bogue sans double.
Mise à jour: Microsoft estime que c'est une bonne pratique pour la sécurité et recommande cette pratique dans ses stratégies SDL. Apparemment, MSVC ++ 11 écrasera automatiquement le pointeur supprimé (dans de nombreuses circonstances) si vous compilez avec l'option / SDL.
la source
Premièrement, il existe de nombreuses questions sur ce sujet et sur des sujets étroitement liés, par exemple Pourquoi la suppression ne définit-elle pas le pointeur sur NULL? .
Dans votre code, le problème de ce qui se passe (utilisez p). Par exemple, si quelque part vous avez du code comme celui-ci:
puis définir p sur NULL ne fait pas grand-chose, car vous avez toujours à vous soucier du pointeur p2.
Cela ne veut pas dire que définir un pointeur sur NULL est toujours inutile. Par exemple, si p était une variable membre pointant vers une ressource dont la durée de vie n'était pas exactement la même que la classe contenant p, définir p sur NULL pourrait être un moyen utile d'indiquer la présence ou l'absence de la ressource.
la source
S'il y a plus de code après le
delete
, Oui. Lorsque le pointeur est supprimé dans un constructeur ou à la fin d'une méthode ou d'une fonction, Non.Le but de cette parabole est de rappeler au programmeur, pendant l'exécution, que l'objet a déjà été supprimé.
Une pratique encore meilleure consiste à utiliser des pointeurs intelligents (partagés ou étendus) qui suppriment automatiquement leurs objets cibles.
la source
Comme d'autres l'ont dit, cela
delete ptr; ptr = 0;
ne fera pas sortir les démons de votre nez. Cependant, cela encourage l'utilisation de enptr
tant que drapeau. Le code est jonché dedelete
et positionnant le pointeur surNULL
. L'étape suivante consiste à disperserif (arg == NULL) return;
votre code pour vous protéger contre l'utilisation accidentelle d'unNULL
pointeur. Le problème se produit une fois que les vérificationsNULL
deviennent votre principal moyen de vérifier l'état d'un objet ou d'un programme.Je suis sûr qu'il y a une odeur de code à propos de l'utilisation d'un pointeur comme indicateur quelque part, mais je n'en ai pas trouvé.
la source
NULL
n'est pas une valeur valide, vous devriez probablement utiliser une référence à la place.Je vais modifier légèrement votre question:
Il existe deux scénarios dans lesquels la définition du pointeur sur NULL peut être ignorée:
Pendant ce temps, dire que définir le pointeur sur NULL pourrait me cacher des erreurs revient à dire que vous ne devriez pas corriger un bogue car le correctif pourrait masquer un autre bogue. Les seuls bogues qui pourraient montrer si le pointeur n'est pas défini sur NULL sont ceux qui essaient d'utiliser le pointeur. Mais le définir sur NULL provoquerait en fait exactement le même bogue que celui indiqué si vous l'utilisiez avec de la mémoire libérée, n'est-ce pas?
la source
Si vous n'avez aucune autre contrainte qui vous oblige à définir ou à ne pas définir le pointeur sur NULL après l'avoir supprimé (une telle contrainte a été mentionnée par Neil Butterworth ), ma préférence personnelle est de la laisser telle quelle.
Pour moi, la question n'est pas "est-ce une bonne idée?" mais "quel comportement empêcherais-je ou autoriserais-je à réussir en faisant cela?" Par exemple, si cela permet à un autre code de voir que le pointeur n'est plus disponible, pourquoi un autre code tente-t-il même de regarder les pointeurs libérés après leur libération? Habituellement, c'est un bug.
Il fait également plus de travail que nécessaire et empêche le débogage post-mortem. Moins vous touchez la mémoire après n'en avoir pas besoin, plus il est facile de comprendre pourquoi quelque chose s'est écrasé. Plusieurs fois, je me suis appuyé sur le fait que la mémoire est dans un état similaire à celui d'un bogue particulier pour diagnostiquer et corriger ledit bogue.
la source
Annulation explicite après suppression suggère fortement au lecteur que le pointeur représente quelque chose qui est conceptuellement facultatif . Si je voyais cela se faire, je commencerais à m'inquiéter du fait que partout dans la source, le pointeur est utilisé pour qu'il soit d'abord testé contre NULL.
Si c'est ce que vous voulez dire, il vaut mieux rendre cela explicite dans la source en utilisant quelque chose comme boost :: facultatif
Mais si vous voulez vraiment que les gens sachent que le pointeur a "mal tourné", je serai d'accord à 100% avec ceux qui disent que la meilleure chose à faire est de le faire sortir du champ d'application. Ensuite, vous utilisez le compilateur pour éviter la possibilité de mauvaises déréférences au moment de l'exécution.
C'est le bébé dans toute l'eau du bain C ++, ne devrait pas le jeter. :)
la source
Dans un programme bien structuré avec une vérification d'erreur appropriée, il n'y a aucune raison de ne pas lui attribuer la valeur null.
0
est la seule valeur invalide universellement reconnue dans ce contexte. Échouer dur et échouer bientôt.De nombreux arguments contre l'attribution
0
suggèrent que cela pourrait masquer un bogue ou compliquer le flux de contrôle. Fondamentalement, c'est soit une erreur en amont (pas votre faute (désolé pour le mauvais jeu de mots)) soit une autre erreur de la part du programmeur - peut-être même une indication que le flux du programme est devenu trop complexe.Si le programmeur veut introduire l'utilisation d'un pointeur qui peut être nul comme valeur spéciale et écrire toute l'esquive nécessaire autour de cela, c'est une complication qu'il a délibérément introduite. Plus la quarantaine est bonne, plus vite vous trouvez des cas d'abus et moins ils sont capables de se propager dans d'autres programmes.
Des programmes bien structurés peuvent être conçus à l'aide des fonctionnalités C ++ pour éviter ces cas. Vous pouvez utiliser des références, ou vous pouvez simplement dire "passer / utiliser des arguments nuls ou non valides est une erreur" - une approche qui s'applique également aux conteneurs, tels que les pointeurs intelligents. Augmenter le comportement cohérent et correct empêche ces bogues d'aller loin.
À partir de là, vous n'avez qu'une portée et un contexte très limités où un pointeur nul peut exister (ou est autorisé).
La même chose peut être appliquée aux pointeurs qui ne le sont pas
const
. Suivre la valeur d'un pointeur est trivial car sa portée est si petite et une utilisation incorrecte est vérifiée et bien définie. Si votre ensemble d'outils et vos ingénieurs ne peuvent pas suivre le programme après une lecture rapide ou s'il y a une vérification d'erreur inappropriée ou un flux de programme incohérent / indulgent, vous avez d'autres problèmes plus importants.Enfin, votre compilateur et votre environnement ont probablement des gardes pour les moments où vous souhaitez introduire des erreurs (griffonnage), détecter les accès à la mémoire libérée et attraper d'autres UB associés. Vous pouvez également introduire des diagnostics similaires dans vos programmes, souvent sans affecter les programmes existants.
la source
Permettez-moi de développer ce que vous avez déjà mis dans votre question.
Voici ce que vous avez mis dans votre question, sous forme de puces:
Définir des pointeurs sur NULL après la suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où:
Cependant, il n'y a pas de moments où c'est mauvais ! Vous n'introduirez pas plus de bogues en l'annulant explicitement, vous ne fuirez pas de mémoire, vous ne provoquerez pas de comportement indéfini .
Donc, en cas de doute, annulez-le.
Cela dit, si vous sentez que vous devez annuler explicitement un pointeur, cela me semble que vous n'avez pas assez divisé une méthode et que vous devriez regarder l'approche de refactoring appelée "méthode d'extraction" pour diviser la méthode en parties séparées.
la source
Oui.
Le seul «mal» qu'il peut faire est d'introduire de l'inefficacité (une opération de stockage inutile) dans votre programme - mais cette surcharge sera insignifiante par rapport au coût d'allocation et de libération du bloc de mémoire dans la plupart des cas.
Si vous ne le faites pas, vous aurez un jour de mauvais bugs de déréférencement de pointeurs.
J'utilise toujours une macro pour supprimer:
(et similaire pour un tableau, free (), libérant des poignées)
Vous pouvez également écrire des méthodes de «suppression automatique» qui prennent une référence au pointeur du code appelant, afin qu'elles forcent le pointeur du code appelant à NULL. Par exemple, pour supprimer un sous-arbre de nombreux objets:
Éditer
Oui, ces techniques violent certaines règles sur l'utilisation des macros (et oui, de nos jours, vous pourriez probablement obtenir le même résultat avec des modèles) - mais en utilisant pendant de nombreuses années, je n'ai jamais accédé à la mémoire morte - l'une des plus méchantes et des plus difficiles et le plus de temps pour déboguer les problèmes auxquels vous pouvez faire face. En pratique, pendant de nombreuses années, ils ont effectivement éliminé toute une classe de bogues de chaque équipe dans laquelle je les ai présentés.
Il existe également de nombreuses façons de mettre en œuvre ce qui précède - j'essaie simplement d'illustrer l'idée de forcer les gens à NULL un pointeur s'ils suppriment un objet, plutôt que de leur fournir un moyen de libérer la mémoire qui ne NULL le pointeur de l'appelant .
Bien sûr, l'exemple ci-dessus n'est qu'un pas vers un pointeur automatique. Ce que je n'ai pas suggéré parce que l'OP posait spécifiquement des questions sur le cas de ne pas utiliser de pointeur automatique.
la source
anObject->Delete(anObject)
invalider leanObject
pointeur. C'est juste effrayant. Vous devez créer une méthode statique pour cela afin que vous soyez obligé de faireTreeItem::Delete(anObject)
au minimum."Il y a des moments où c'est une bonne chose à faire, et des moments où cela ne sert à rien et peut cacher des erreurs"
Je peux voir deux problèmes: Ce code simple:
devient un for-liner dans un environnement multithread:
Les «meilleures pratiques» de Don Neufeld ne s'appliquent pas toujours. Par exemple, dans un projet automobile, nous avons dû mettre les pointeurs à 0, même dans les destructeurs. J'imagine que dans les logiciels critiques pour la sécurité, de telles règles ne sont pas rares. Il est plus facile (et sage) de les suivre que d'essayer de persuader l'équipe / le vérificateur de code pour chaque utilisation de pointeur dans le code, qu'une ligne annulant ce pointeur est redondante.
Un autre danger est de s'appuyer sur cette technique dans le code utilisant des exceptions:
Dans un tel code, soit vous produisez une fuite de ressources et reportez le problème, soit le processus se bloque.
Donc, ces deux problèmes qui me traversent spontanément la tête (Herb Sutter en dirait certainement plus) font pour moi toutes les questions du genre «Comment éviter d'utiliser des pointeurs intelligents et faire le travail en toute sécurité avec des pointeurs normaux» comme obsolètes.
la source
Il y a toujours des pointeurs suspendus à s'inquiéter.
la source
Si vous allez réallouer le pointeur avant de l'utiliser à nouveau (le déréférencer, le passer à une fonction, etc.), rendre le pointeur NULL est juste une opération supplémentaire. Cependant, si vous ne savez pas s'il sera réalloué ou non avant d'être réutilisé, le définir sur NULL est une bonne idée.
Comme beaucoup l'ont dit, il est bien sûr beaucoup plus facile d'utiliser simplement des pointeurs intelligents.
Edit: Comme Thomas Matthews l'a dit dans cette réponse précédente , si un pointeur est supprimé dans un destructeur, il n'est pas nécessaire de lui affecter NULL car il ne sera plus utilisé car l'objet est déjà détruit.
la source
Je peux imaginer définir un pointeur sur NULL après sa suppression, ce qui est utile dans de rares cas où il existe un scénario légitime de réutilisation dans une seule fonction (ou objet). Sinon, cela n'a aucun sens - un pointeur doit pointer vers quelque chose de significatif tant qu'il existe - point final.
la source
Si le code n'appartient pas à la partie la plus critique des performances de votre application, restez simple et utilisez un shared_ptr:
Il effectue le comptage des références et est thread-safe. Vous pouvez le trouver dans le tr1 (espace de noms std :: tr1, #include <mémoire>) ou si votre compilateur ne le fournit pas, récupérez-le à partir de boost.
la source