Définition de la variable NULL après la libération

156

Dans mon entreprise, il existe une règle de codage qui dit, après avoir libéré de la mémoire, réinitialiser la variable sur NULL. Par exemple ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Je pense que, dans des cas comme le code ci-dessus, définir sur NULLn'a aucun sens. Ou est-ce que je manque quelque chose?

S'il n'y a pas de sens dans de tels cas, je vais m'en occuper avec "l'équipe qualité" pour supprimer cette règle de codage. S'il vous plaît des conseils.

Alphaneo
la source
3
il est toujours utile de pouvoir le vérifier ptr == NULLavant d'en faire quoi que ce soit. Si vous n'annulez pas vos pointeurs gratuits, vous obtiendrez un ptr != NULLpointeur inutilisable.
Ki Jéy
Les pointeurs suspendus peuvent conduire à des vulnérabilités exploitables telles que Use-After-Free .
Константин Ван

Réponses:

285

Définir des pointeurs inutilisés sur NULL est un style défensif, protégeant contre les bogues de pointeur pendant. Si un pointeur suspendu est accédé après sa libération, vous pouvez lire ou écraser la mémoire aléatoire. Si un pointeur nul est accédé, vous obtenez un crash immédiat sur la plupart des systèmes, vous indiquant immédiatement quelle est l'erreur.

Pour les variables locales, cela peut être un peu inutile s'il est "évident" que le pointeur n'est plus accédé après avoir été libéré, donc ce style est plus approprié pour les données membres et les variables globales. Même pour les variables locales, cela peut être une bonne approche si la fonction continue après la libération de la mémoire.

Pour terminer le style, vous devez également initialiser les pointeurs à NULL avant qu'ils ne reçoivent une valeur de pointeur vraie.

Martin c.Löwis
la source
3
Je ne comprends pas pourquoi vous «initialiseriez les pointeurs à NULL avant qu'ils ne reçoivent une vraie valeur de pointeur»?
Paul Biggar
26
@Paul: Dans le cas spécifique, la déclaration pourrait lire int *nPtr=NULL;. Maintenant, je conviens que ce serait redondant, avec un malloc suivant à droite dans la ligne suivante. Cependant, s'il y a du code entre la déclaration et la première initialisation, quelqu'un peut commencer à utiliser la variable même si elle n'a pas encore de valeur. Si vous initialisez à zéro, vous obtenez le segfault; sans, vous pourriez à nouveau lire ou écrire de la mémoire aléatoire. De même, si la variable n'est initialisée que conditionnellement par la suite, les accès défectueux ultérieurs devraient vous donner des plantages instantanés si vous vous êtes souvenu d'initialiser à null.
Martin c.Löwis
1
Je pense personnellement que dans toute base de code non triviale, obtenir une erreur pour déréférencer null est aussi vague que d'obtenir une erreur pour déréférencer une adresse que vous ne possédez pas. Personnellement, je ne me dérange jamais.
wilhelmtell
9
Wilhelm, le fait est qu'avec un déréférencement de pointeur nul, vous obtenez un crash déterminé et l'emplacement réel du problème. Un accès incorrect peut se bloquer ou non et corrompre les données ou le comportement de manière inattendue dans des endroits inattendus.
Amit Naidu le
4
En fait, l'initialisation du pointeur sur NULL a au moins un inconvénient important: cela peut empêcher le compilateur de vous avertir des variables non initialisées. À moins que la logique de votre code ne gère explicitement cette valeur pour le pointeur (c'est-à-dire si (nPtr == NULL) dosomething ...), il vaut mieux la laisser telle quelle.
Eric
37

Définir un pointeur vers NULLaprès freeest une pratique douteuse qui est souvent popularisée comme une règle de «bonne programmation» sur une prémisse manifestement fausse. C'est une de ces fausses vérités qui appartiennent à la catégorie des «sons corrects» mais qui n'obtiennent en réalité absolument rien d'utile (et conduisent parfois à des conséquences négatives).

Prétendument, définir un pointeur sur NULLafter freeest censé empêcher le redoutable problème de "double free" lorsque la même valeur de pointeur est transmise à freeplusieurs reprises. En réalité cependant, dans 9 cas sur 10, le vrai problème de "double libre" se produit lorsque différents objets pointeurs contenant la même valeur de pointeur sont utilisés comme arguments pour free. Inutile de dire que définir un pointeur sur NULLaprès freen'apporte absolument rien pour éviter le problème dans de tels cas.

Bien sûr, il est possible de rencontrer un problème de "double libre" en utilisant le même objet pointeur comme argument de free. Cependant, dans la réalité, des situations comme celle-là indiquent normalement un problème avec la structure logique générale du code, et non un simple "double libre" accidentel. Une bonne façon de traiter le problème dans de tels cas est de revoir et de repenser la structure du code afin d'éviter la situation où le même pointeur est passé à freeplusieurs reprises. Dans de tels cas, placer le pointeur sur NULLet considérer le problème comme "résolu" n'est rien de plus qu'une tentative de balayer le problème sous le tapis. Cela ne fonctionnera tout simplement pas dans le cas général, car le problème avec la structure du code trouvera toujours un autre moyen de se manifester.

Enfin, si votre code est spécifiquement conçu pour s'appuyer sur la valeur du pointeur NULLou non NULL, il est parfaitement normal de définir la valeur du pointeur NULLaprès free. Mais en tant que règle générale de «bonne pratique» (comme dans «toujours placer votre pointeur sur NULLaprès free»), il s'agit, encore une fois, d'un faux bien connu et assez inutile, souvent suivi par certains pour des raisons purement religieuses, de type vaudou.

Fourmi
la source
1
Absolument. Je ne me souviens pas avoir causé un double-free qui serait corrigé en définissant le pointeur sur NULL après la libération, mais j'ai causé beaucoup de choses qui ne le seraient pas.
LnxPrgr3
4
@AnT "douteux" est un peu trop. Tout dépend du cas d'utilisation. Si la valeur du pointeur est jamais utilisée dans un sens vrai / faux, ce n'est pas seulement une pratique valide, c'est une meilleure pratique.
Coder
1
@Coder Complètement faux. Si la valeur du pointeur est utilisée dans un vrai faux sens pour savoir s'il pointait ou non vers un objet avant l'appel à free, ce n'est pas seulement la meilleure pratique, c'est faux . Par exemple: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Ici, la mise barà NULLaprès l'appel à freela fonction va penser qu'il n'a jamais eu un bar et retourner la valeur faux!
David Schwartz
Je ne pense pas que le principal avantage soit de se protéger contre un double gratuit, mais plutôt d'attraper les pointeurs pendants plus tôt et de manière plus fiable. Par exemple, lorsque je libère une structure contenant des ressources, des pointeurs vers la mémoire allouée, des descripteurs de fichiers, etc., lorsque je libère les pointeurs de mémoire contenus et ferme les fichiers contenus, je NULL les membres respectifs. Ensuite, si l'une des ressources est accédée par erreur via un pointeur suspendu, le programme a tendance à faire une erreur là-bas, à chaque fois. Sinon, sans NULLing, les données libérées pourraient ne pas encore être écrasées et le bogue pourrait ne pas être facilement reproductible.
jimhark
1
Je suis d'accord qu'un code bien structuré ne devrait pas permettre le cas où un pointeur est accédé après avoir été libéré ou le cas où il est libéré deux fois. Mais dans le monde réel, mon code sera modifié et / ou maintenu par quelqu'un qui ne me connaît probablement pas et qui n'a pas le temps et / ou les compétences pour faire les choses correctement (car la date limite est toujours hier). Par conséquent, j'ai tendance à écrire des fonctions à l'épreuve des balles qui ne plantent pas le système même si elles sont mal utilisées.
mfloris
35

La plupart des réponses se sont concentrées sur la prévention d'un double libre, mais définir le pointeur sur NULL présente un autre avantage. Une fois que vous libérez un pointeur, cette mémoire est disponible pour être réallouée par un autre appel à malloc. Si vous avez toujours le pointeur d'origine autour de vous, vous pourriez vous retrouver avec un bogue où vous essayez d'utiliser le pointeur après avoir libéré et corrompu une autre variable, puis votre programme entre dans un état inconnu et toutes sortes de mauvaises choses peuvent se produire (planter si vous êtes chanceux, corruption de données si vous n'avez pas de chance). Si vous aviez défini le pointeur sur NULL après la libération, toute tentative de lecture / écriture à travers ce pointeur ultérieurement entraînerait une erreur de segmentation, ce qui est généralement préférable à une corruption de mémoire aléatoire.

Pour les deux raisons, il peut être judicieux de définir le pointeur sur NULL après free (). Ce n'est pas toujours nécessaire, cependant. Par exemple, si la variable pointeur sort de la portée immédiatement après free (), il n'y a pas beaucoup de raison de la définir sur NULL.

Mike McNertney
la source
1
+1 C'est en fait un très bon point. Pas le raisonnement sur le "double gratuit" (qui est complètement faux), mais ceci . Je ne suis pas fan de NULL-ing mécanique de pointeurs après free, mais cela a du sens.
AnT
Si vous pouviez accéder à un pointeur après l'avoir libéré via ce même pointeur, il est encore plus probable que vous accédiez à un pointeur après avoir libéré l'objet vers lequel il pointe via un autre pointeur. Cela ne vous aide donc pas du tout - vous devez toujours utiliser un autre mécanisme pour vous assurer de ne pas accéder à un objet via un pointeur après l'avoir libéré via un autre. Vous pouvez également utiliser cette méthode pour protéger dans le même cas de pointeur.
David Schwartz
1
@DavidSchwartz: Je ne suis pas d'accord avec votre commentaire. Quand j'ai dû écrire une pile pour un exercice universitaire il y a quelques semaines, j'ai eu un problème, j'ai enquêté quelques heures. J'ai accédé à de la mémoire déjà libre à un moment donné (le libre était quelques lignes trop tôt). Et parfois, cela conduit à un comportement très étrange. Si j'avais mis le pointeur sur NULL après l'avoir libéré, il y aurait eu un segfault "simple" et j'aurais économisé quelques heures de travail. Donc +1 pour cette réponse!
mozzbozz
2
@katze_sonne Même une horloge arrêtée a raison deux fois par jour. Il est beaucoup plus probable que la définition des pointeurs sur NULL masquera les bogues en empêchant les accès erronés aux objets déjà libérés de segfaulting dans le code qui vérifie NULL puis échoue silencieusement à vérifier un objet qu'il aurait dû vérifier. (Peut-être que définir des pointeurs sur NULL après free dans des versions de débogage spécifiques peut être utile, ou les définir sur une valeur autre que NULL qui est garantie pour segfault peut avoir du sens. Mais que cette bêtise vous ait aidé une fois n'est pas un argument en sa faveur .)
David Schwartz
@DavidSchwartz Eh bien, cela semble raisonnable ... Merci pour votre commentaire, je vais considérer cela à l'avenir! :) +1
mozzbozz
20

Ceci est considéré comme une bonne pratique pour éviter d'écraser la mémoire. Dans la fonction ci-dessus, ce n'est pas nécessaire, mais souvent, lorsque cela est fait, il peut trouver des erreurs d'application.

Essayez plutôt quelque chose comme ceci:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** p_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

DEBUG_VERSION vous permet de libérer le profil dans le code de débogage, mais les deux sont fonctionnellement identiques.

Edit : Ajout de faire ... comme suggéré ci-dessous, merci.

ébloui
la source
3
La version macro a un bogue subtil si vous l'utilisez après une instruction if sans crochets.
Mark Ransom
Qu'est-ce que le (vide) 0? Ce code fait: if (x) myfree (& x); else do_foo (); devient if (x) {free (* (& x)); * (& x) = nul; } void 0; else do_foo (); L'autre est une erreur.
jmucchiello
Cette macro est un endroit parfait pour l'opérateur virgule: free ( (p)), * (p) = null. Bien sûr, le problème suivant est qu'il évalue * (p) deux fois. Ce devrait être {void * _pp = (p); gratuit (* _ pp); * _pp = null; } Le préprocesseur n'est pas amusant.
jmucchiello
5
La macro ne doit pas être entre crochets, elle doit être dans un do { } while(0)bloc pour if(x) myfree(x); else dostuff();ne pas casser.
Chris Lutz
3
Comme l'a dit Lutz, le corps macro do {X} while (0)est le meilleur moyen pour l'OMI de créer un corps macro qui "ressemble et fonctionne comme" une fonction. La plupart des compilateurs optimisent quand même la boucle.
Mike Clark
7

Si vous atteignez le pointeur qui a été free () d, il peut se casser ou non. Cette mémoire peut être réallouée à une autre partie de votre programme et vous obtenez alors une corruption de mémoire,

Si vous définissez le pointeur sur NULL, si vous y accédez, le programme se bloque toujours avec un segfault. Plus de ,, parfois ça marche '', plus de `` plantages de manière imprévisible ''. C'est beaucoup plus facile à déboguer.

Tadeusz A. Kadłubowski
la source
5
Le programme ne plante pas toujours avec un segfault. Si la façon dont vous accédez au pointeur signifie qu'un décalage suffisamment grand lui est appliqué avant le déréférencement, il peut atteindre la mémoire adressable: ((MyHugeStruct *) 0) -> fieldNearTheEnd. Et c'est même avant de traiter avec du matériel qui ne se sépare pas du tout de l'accès 0. Cependant, le programme est plus susceptible de planter avec un segfault.
Steve Jessop
7

Définir le pointeur sur la freemémoire 'd signifie que toute tentative d'accès à cette mémoire via le pointeur plantera immédiatement, au lieu de provoquer un comportement indéfini. Il est beaucoup plus facile de déterminer où les choses ont mal tourné.

Je peux voir votre argument: comme cela nPtrsort juste après nPtr = NULL, il ne semble pas y avoir de raison de le définir NULL. Cependant, dans le cas d'un structmembre ou ailleurs où le pointeur n'est pas immédiatement hors de portée, cela a plus de sens. Il n'est pas immédiatement évident si ce pointeur sera à nouveau utilisé par un code qui ne devrait pas l'utiliser.

Il est probable que la règle soit énoncée sans faire de distinction entre ces deux cas, car il est beaucoup plus difficile d'appliquer automatiquement la règle, et encore moins pour les développeurs de la suivre. Cela ne fait pas de mal de définir des pointeurs NULLaprès chaque gratuit, mais cela a le potentiel de signaler de gros problèmes.

Jared Oberhaus
la source
7

le bogue le plus courant en c est le double free. En gros, tu fais quelque chose comme ça

free(foobar);
/* lot of code */
free(foobar);

et cela finit plutôt mal, le système d'exploitation essaie de libérer de la mémoire déjà libérée et généralement il segfault. Donc, la bonne pratique est de régler sur NULL, afin que vous puissiez faire un test et vérifier si vous avez vraiment besoin de libérer cette mémoire

if(foobar != null){
  free(foobar);
}

Notez également que free(NULL)cela ne fera rien, vous n'avez donc pas à écrire l'instruction if. Je ne suis pas vraiment un gourou du système d'exploitation, mais je suis assez, même maintenant, la plupart des systèmes d'exploitation planteraient en double free.

C'est aussi une des raisons principales pour lesquelles tous les langages avec garbage collection (Java, dotnet) étaient si fiers de ne pas avoir ce problème et de ne pas avoir à laisser aux développeurs la gestion de la mémoire dans son ensemble.

RageZ
la source
11
Vous pouvez en fait appeler free () sans vérification - free (NULL) est défini comme ne rien faire.
Ambre
5
Cela ne cache-t-il pas des bugs? (Comme libérer trop.)
Georg Schölly
1
merci, je l'ai. J'ai essayé:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang
5
Comme je l'ai dit, free(void *ptr) ne peut pas changer la valeur du pointeur, il est passé. Il peut modifier le contenu du pointeur, les données stockées à cette adresse , mais pas l' adresse elle - même ou la valeur du pointeur . Cela nécessiterait free(void **ptr)(ce qui n'est apparemment pas autorisé par la norme) ou une macro (qui est autorisée et parfaitement portable mais les gens n'aiment pas les macros). De plus, C n'est pas une question de commodité, il s'agit de donner aux programmeurs autant de contrôle qu'ils le souhaitent. S'ils ne veulent pas de la surcharge de réglage des pointeurs NULL, cela ne devrait pas leur être imposé.
Chris Lutz
2
Il y a peu de choses dans le monde qui révèlent le manque de professionnalisme de l'auteur du code C. Mais ils incluent "vérifier le pointeur pour NULL avant d'appeler free" (avec des choses telles que "transtyper le résultat des fonctions d'allocation de mémoire" ou "utiliser sans réfléchir des noms de types avec sizeof").
AnT
6

L'idée derrière cela, est d'arrêter la réutilisation accidentelle du pointeur libéré.

Blé Mitch
la source
4

Cela (peut) être important. Bien que vous libériez la mémoire, une partie ultérieure du programme pourrait allouer quelque chose de nouveau qui arrive à atterrir dans l'espace. Votre ancien pointeur pointerait maintenant vers un bloc de mémoire valide. Il est alors possible que quelqu'un utilise le pointeur, ce qui entraîne un état de programme invalide.

Si vous NULL le pointeur, alors toute tentative de l'utiliser va déréférencer 0x0 et planter juste là, ce qui est facile à déboguer. Les pointeurs aléatoires pointant vers la mémoire aléatoire sont difficiles à déboguer. Ce n'est évidemment pas nécessaire, mais c'est pourquoi c'est dans un document sur les meilleures pratiques.

Steven Canfield
la source
Sur Windows, au moins, les versions de débogage définiront la mémoire sur 0xdddddddd, donc lorsque vous utilisez un pointeur vers la mémoire supprimée, vous le savez immédiatement. Il devrait y avoir des mécanismes similaires sur toutes les plates-formes.
i_am_jorf
2
jeffamaphone, un bloc de mémoire supprimé peut avoir été réalloué et attribué à un autre objet au moment où vous utilisez à nouveau le pointeur.
Constantin
4

De la norme ANSI C:

void free(void *ptr);

La fonction free provoque la désallocation de l'espace pointé par ptr, c'est-à-dire la mise à disposition pour une allocation ultérieure. Si ptr est un pointeur nul, aucune action ne se produit. Sinon, si l'argument ne correspond pas à un pointeur précédemment renvoyé par la fonction calloc, malloc ou realloc, ou si l'espace a été libéré par un appel à free ou realloc, le comportement n'est pas défini.

"le comportement indéfini" est presque toujours un crash de programme. Afin d'éviter cela, il est prudent de réinitialiser le pointeur sur NULL. free () lui-même ne peut pas faire cela car il n'est passé qu'un pointeur, pas un pointeur vers un pointeur. Vous pouvez également écrire une version plus sûre de free () qui NULL le pointeur:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
Vijay Mathew
la source
@DrPizza - Une erreur (à mon avis) est quelque chose qui empêche votre programme de fonctionner comme il le devrait. Si un double gratuit caché casse votre programme, c'est une erreur. Si cela fonctionne exactement comme prévu, ce n'est pas une erreur.
Chris Lutz
@DrPizza: Je viens de trouver un argument pour lequel on devrait le définir NULLpour éviter de masquer les erreurs. stackoverflow.com/questions/1025589/... Il semble que dans les deux cas, certaines erreurs soient masquées.
Georg Schölly
1
Sachez qu'un pointeur vers pointeur vide a ses problèmes: c-faq.com/ptrs/genericpp.html
Sécurisé
3
@Chris, non, la meilleure approche est la structure du code. Ne lancez pas de mallocs aléatoires et libère partout dans votre base de code, gardez les choses liées ensemble. Le "module" qui alloue une ressource (mémoire, fichier, ...) est chargé de la libérer et doit fournir une fonction pour ce faire qui garde aussi le soin des pointeurs. Pour toute ressource spécifique, vous avez alors exactement un endroit où elle est allouée et un endroit où elle est libérée, tous deux rapprochés.
Sécurisé
4
@Chris Lutz: Hogwash. Si vous écrivez du code qui libère le même pointeur deux fois, votre programme contient une erreur logique. Masquer cette erreur logique en la faisant ne pas planter ne signifie pas que le programme est correct: il fait toujours quelque chose d'absolument. Il n'y a pas de scénario dans lequel l'écriture d'un double gratuit est justifiée.
DrPizza
4

Je trouve que cela n'aide guère car dans mon expérience, lorsque les gens accèdent à une allocation de mémoire libérée, c'est presque toujours parce qu'ils ont un autre pointeur quelque part. Et puis cela entre en conflit avec une autre norme de codage personnelle qui est "Évitez l'encombrement inutile", donc je ne le fais pas car je pense que cela aide rarement et rend le code un peu moins lisible.

Cependant - je ne définirai pas la variable sur null si le pointeur n'est pas censé être utilisé à nouveau, mais souvent la conception de niveau supérieur me donne une raison de le définir sur null de toute façon. Par exemple, si le pointeur est un membre d'une classe et que j'ai supprimé ce qu'il pointe, le "contrat" ​​si vous aimez la classe est que ce membre pointera vers quelque chose de valide à tout moment, il doit donc être défini sur null pour cette raison. Une petite distinction mais je pense que c'est important.

En C ++, il est important de toujours penser à qui possède ces données lorsque vous allouez de la mémoire (à moins que vous n'utilisiez des pointeurs intelligents, mais même dans ce cas, une réflexion est nécessaire). Et ce processus a tendance à conduire à ce que les pointeurs soient généralement membres d'une classe et que vous voulez généralement qu'une classe soit dans un état valide à tout moment, et le moyen le plus simple de le faire est de définir la variable membre sur NULL pour indiquer qu'elle pointe à rien maintenant.

Un modèle courant consiste à définir tous les pointeurs membres sur NULL dans le constructeur et à faire supprimer l'appel du destructeur sur tous les pointeurs vers les données que votre conception indique que la classe possède . Clairement, dans ce cas, vous devez définir le pointeur sur NULL lorsque vous supprimez quelque chose pour indiquer que vous ne possédez aucune donnée auparavant.

Donc, pour résumer, oui, je règle souvent le pointeur sur NULL après avoir supprimé quelque chose, mais c'est dans le cadre d'une conception plus large et de réflexions sur le propriétaire des données plutôt que de suivre aveuglément une règle standard de codage. Je ne le ferais pas dans votre exemple car je pense qu'il n'y a aucun avantage à le faire et cela ajoute du «fouillis» qui, d'après mon expérience, est tout aussi responsable des bogues et du mauvais code que ce genre de chose.

jcoder
la source
4

Récemment, je suis tombé sur la même question après avoir cherché la réponse. Je suis arrivé à cette conclusion:

C'est la meilleure pratique, et il faut la suivre pour la rendre portable sur tous les systèmes (embarqués).

free()est une fonction de bibliothèque, qui varie au fur et à mesure que l'on change de plate-forme, il ne faut donc pas s'attendre à ce qu'après avoir passé le pointeur vers cette fonction et après avoir libéré de la mémoire, ce pointeur soit mis à NULL. Cela peut ne pas être le cas pour certaines bibliothèques implémentées pour la plate-forme.

alors vas-y toujours

free(ptr);
ptr = NULL;
Jalindar
la source
3

Cette règle est utile lorsque vous essayez d'éviter les scénarios suivants:

1) Vous avez une fonction vraiment longue avec une logique compliquée et une gestion de la mémoire et vous ne voulez pas réutiliser accidentellement le pointeur vers la mémoire supprimée plus tard dans la fonction.

2) Le pointeur est une variable membre d'une classe qui a un comportement assez complexe et vous ne voulez pas réutiliser accidentellement le pointeur vers la mémoire supprimée dans d'autres fonctions.

Dans votre scénario, cela n'a pas beaucoup de sens, mais si la fonction devait s'allonger, cela pourrait avoir de l'importance.

Vous pouvez affirmer que le définir sur NULL peut en fait masquer les erreurs de logique plus tard, ou dans le cas où vous supposez qu'il est valide, vous plantez toujours sur NULL, donc cela n'a pas d'importance.

En général, je vous conseillerais de le définir sur NULL lorsque vous pensez que c'est une bonne idée, et de ne pas vous inquiéter lorsque vous pensez que cela ne vaut pas la peine. Concentrez-vous plutôt sur l'écriture de fonctions courtes et de classes bien conçues.

i_am_jorf
la source
2

Pour ajouter à ce que d'autres ont dit, une bonne méthode d'utilisation du pointeur est de toujours vérifier s'il s'agit d'un pointeur valide ou non. Quelque chose comme:


if(ptr)
   ptr->CallSomeMethod();

Le marquage explicite du pointeur comme NULL après sa libération permet ce type d'utilisation en C / C ++.

Aamir
la source
5
Dans de nombreux cas, lorsqu'un pointeur NULL n'a aucun sens, il serait préférable d'écrire une assertion à la place.
Erich Kitzmueller
2

Cela pourrait être plus un argument pour initialiser tous les pointeurs à NULL, mais quelque chose comme ça peut être un bogue très sournois:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

pse termine au même endroit sur la pile que l'ancien nPtr, il peut donc contenir un pointeur apparemment valide. Assigner à *ppourrait écraser toutes sortes de choses sans rapport et conduire à des bugs horribles. Surtout si le compilateur initialise les variables locales avec zéro en mode débogage mais pas une fois que les optimisations sont activées. Ainsi, les versions de débogage ne montrent aucun signe du bogue tandis que les versions de version explosent au hasard ...

qc
la source
2

Définir le pointeur qui vient d'être libéré sur NULL n'est pas obligatoire mais une bonne pratique. De cette façon, vous pouvez éviter 1) d'utiliser une pointe libérée 2) de la libérer

Pierrotlefou
la source
2

Un pointeur vers NULL sert à protéger contre ce que l'on appelle le double-free - une situation où free () est appelé plus d'une fois pour la même adresse sans réallouer le bloc à cette adresse.

Double-free conduit à un comportement indéfini - généralement une corruption de tas ou une panne immédiate du programme. Appeler free () pour un pointeur NULL ne fait rien et est donc garanti.

Donc, la meilleure pratique à moins que vous ne soyez maintenant sûr que le pointeur quitte la portée immédiatement ou très peu de temps après free () est de définir ce pointeur sur NULL afin que même si free () est appelé à nouveau, il est maintenant appelé pour un pointeur NULL et un comportement non défini est éludé.

dents acérées
la source
2

L'idée est que si vous essayez de déréférencer le pointeur qui n'est plus valide après l'avoir libéré, vous voulez échouer dur (segfault) plutôt que silencieusement et mystérieusement.

Mais fais attention. Tous les systèmes ne provoquent pas de segfault si vous déréférencer NULL. Sous (au moins certaines versions de) AIX, * (int *) 0 == 0, et Solaris a une compatibilité facultative avec cette "fonctionnalité" AIX.

Jaap Weel
la source
2

À la question d'origine: définir le pointeur sur NULL directement après avoir libéré le contenu est une perte de temps complète, à condition que le code réponde à toutes les exigences, soit entièrement débogué et ne sera plus jamais modifié. D'un autre côté, NULL défensivement un pointeur qui a été libéré peut être très utile lorsque quelqu'un ajoute sans réfléchir un nouveau bloc de code sous free (), lorsque la conception du module d'origine n'est pas correcte, et dans le cas de celui-ci -compile-mais-ne-fait-pas-ce-que-je-veux bogues.

Dans n'importe quel système, il y a un objectif impossible à atteindre de rendre plus facile la bonne chose, et le coût irréductible des mesures inexactes. En C, on nous propose un ensemble d'outils très tranchants et très puissants, qui peuvent créer beaucoup de choses entre les mains d'un ouvrier qualifié et infliger toutes sortes de blessures métaphoriques lorsqu'ils sont mal manipulés. Certains sont difficiles à comprendre ou à utiliser correctement. Et les gens, étant naturellement réticents au risque, font des choses irrationnelles comme vérifier un pointeur pour la valeur NULL avant d'appeler gratuitement avec lui ...

Le problème de la mesure est que chaque fois que vous essayez de séparer le bien du moins bien, plus le cas est complexe, plus il est probable que vous obteniez une mesure ambiguë. Si l'objectif est de ne garder que les bonnes pratiques, alors certaines ambiguës sont rejetées avec ce qui n'est pas bon. SI votre objectif est d'éliminer le mauvais, alors les ambiguïtés peuvent rester avec le bien. Les deux objectifs, ne garder que le bien ou éliminer clairement le mauvais, semblent diamétralement opposés, mais il y a généralement un troisième groupe qui n'est ni l'un ni l'autre, certains des deux.

Avant de présenter un dossier avec le service qualité, essayez de parcourir la base de données de bogues pour voir à quelle fréquence, voire jamais, des valeurs de pointeur non valides ont causé des problèmes qui ont dû être notés. Si vous voulez faire une réelle différence, identifiez le problème le plus courant dans votre code de production et proposez trois façons de le prévenir

Projet de loi IV
la source
Bonne réponse. J'aimerais ajouter une chose. L'examen de la base de données des bogues est bon pour diverses raisons. Mais dans le contexte de la question originale, gardez à l'esprit qu'il serait difficile de savoir combien de problèmes de pointeurs invalides ont été évités, ou du moins détectés si tôt pour ne pas avoir été intégrés dans la base de données de bogues. L'historique des bogues fournit de meilleures preuves pour l'ajout de règles de codage.
jimhark
2

Il y a deux raisons:

Évitez les plantages lors de la double libération

Écrit par RageZ dans une question en double .

Le bogue le plus courant en c est le double free. En gros, tu fais quelque chose comme ça

free(foobar);
/* lot of code */
free(foobar);

et cela finit plutôt mal, le système d'exploitation essaie de libérer de la mémoire déjà libérée et généralement il segfault. Donc, la bonne pratique est de régler sur NULL, afin que vous puissiez faire un test et vérifier si vous avez vraiment besoin de libérer cette mémoire

if(foobar != NULL){
  free(foobar);
}

Notez également que free(NULL) cela ne fera rien, vous n'avez donc pas à écrire l'instruction if. Je ne suis pas vraiment un gourou du système d'exploitation, mais je suis assez, même maintenant, la plupart des systèmes d'exploitation planteraient en double free.

C'est aussi une des raisons principales pour lesquelles tous les langages avec garbage collection (Java, dotnet) étaient si fiers de ne pas avoir ce problème et de ne pas avoir à laisser aux développeurs la gestion de la mémoire dans son ensemble.

Évitez d'utiliser des pointeurs déjà libérés

Écrit par Martin c. Löwis dans une autre réponse .

Définir des pointeurs inutilisés sur NULL est un style défensif, protégeant contre les bogues de pointeur pendant. Si un pointeur suspendu est accédé après sa libération, vous pouvez lire ou écraser la mémoire aléatoire. Si un pointeur nul est accédé, vous obtenez un crash immédiat sur la plupart des systèmes, vous indiquant immédiatement quelle est l'erreur.

Pour les variables locales, cela peut être un peu inutile s'il est "évident" que le pointeur n'est plus accédé après avoir été libéré, donc ce style est plus approprié pour les données membres et les variables globales. Même pour les variables locales, cela peut être une bonne approche si la fonction continue après la libération de la mémoire.

Pour terminer le style, vous devez également initialiser les pointeurs à NULL avant qu'ils ne reçoivent une valeur de pointeur vraie.

Georg Schölly
la source
1

Comme vous avez une équipe d'assurance qualité en place, permettez-moi d'ajouter un point mineur sur l'AQ. Certains outils d'assurance qualité automatisés pour C marqueront les affectations aux pointeurs libérés comme "affectation inutile à ptr". Par exemple, PC-lint / FlexeLint de Gimpel Software dit tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

Il existe des moyens de supprimer les messages de manière sélective, de sorte que vous puissiez toujours satisfaire les deux exigences de contrôle qualité, si votre équipe le décide.

Jens
la source
1

Il est toujours conseillé de déclarer une variable de pointeur avec NULL telle que,

int *ptr = NULL;

Disons que ptr pointe vers l' adresse mémoire 0x1000 . Après utilisation free(ptr), il est toujours conseillé d'annuler la variable de pointeur en déclarant à nouveau NULL . par exemple:

free(ptr);
ptr = NULL;

Si elle n'est pas re-déclarée à NULL , la variable de pointeur continue de pointer vers la même adresse ( 0x1000 ), cette variable de pointeur est appelée un pointeur pendant . Si vous définissez une autre variable de pointeur (disons q ) et allouez dynamiquement une adresse au nouveau pointeur, il y a une chance de prendre la même adresse ( 0x1000 ) par une nouvelle variable de pointeur. Si dans le cas, vous utilisez le même pointeur ( ptr ) et mettez à jour la valeur à l'adresse pointée par le même pointeur ( ptr ), alors le programme finira par écrire une valeur à l'endroit où q pointe (puisque p et q sont pointant vers la même adresse (0x1000 )).

par exemple

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.
Pankaj Kumar Thapa
la source
1

En bref: vous ne voulez pas accéder accidentellement (par erreur) à l'adresse que vous avez libérée. Parce que, lorsque vous libérez l'adresse, vous autorisez cette adresse dans le tas à être allouée à une autre application.

Toutefois, si vous ne définissez pas le pointeur sur NULL et que vous essayez par erreur de dé-référencer le pointeur ou de modifier la valeur de cette adresse; VOUS POUVEZ TOUJOURS LE FAIRE. MAIS PAS QUELQUE CHOSE QUE VOUS VOULEZ LOGIQUEMENT FAIRE.

Pourquoi puis-je toujours accéder à l'emplacement mémoire que j'ai libéré? Parce que: Vous avez peut-être libéré de la mémoire, mais la variable de pointeur contenait toujours des informations sur l'adresse mémoire du tas. Donc, en tant que stratégie défensive, veuillez le définir sur NULL.

Ehsan
la source