Paramètre variable à NULL après free
dans mon entreprise il y a une règle de codage qui dit, Après avoir libéré toute mémoire, réinitialiser la variable à NULL. Exemple. ..
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
je pense que, dans les cas comme le code ci-dessus, définir à NULL n'a pas de sens. Ou ai-je raté quelque chose?
S'il n'y a pas de sens dans de tels cas, je vais m'adresser à l '"équipe qualité" pour supprimer cette règle de codage. S'il vous plaît conseils.
23 réponses
placer les pointeurs inutilisés à NULL est un style défensif, protégeant contre les bugs pointeurs pendants. Si un pointeur pendulaire est accédé après qu'il est libéré, 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 ce qu'est l'erreur.
pour les variables locales, il 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 des membres et des variables globales. Même pour les variables locales, il peut être une bonne approche si la fonction continue après que la mémoire est libérée.
pour compléter le style, vous devriez aussi initialiser les pointeurs à NULL avant qu'on leur assigne une valeur de pointeur vrai.
Paramètre un pointeur vers NULL
après free
est une pratique douteuse qui est souvent popularisé comme une "bonne programmation" règle manifestement fausse prémisse. C'est l'une de ces fausses vérités qui appartiennent à la catégorie des "sonorités" mais qui, en réalité, n'atteignent absolument rien d'utile (et qui conduisent parfois à des conséquences négatives).
est supposé empêcher le redoutable problème "double liberté" de pointer vers NULL
après free
lorsque la même valeur de pointeur est passée à free
plus d'une fois. En réalité cependant, dans 9 cas sur 10, le véritable problème "double free" se produit lorsque des objets pointeurs différents ayant la même valeur de pointeur sont utilisés comme arguments pour free
. Inutile de dire que mettre un pointeur sur NULL
après free
ne fait absolument rien pour prévenir le problème dans de tels cas.
bien sûr, il est possible de courir dans " double gratuit" problème lors de l'utilisation du même objet pointeur comme argument vers free
. Cependant, dans la réalité des situations comme celle-ci indiquent normalement un problème avec la structure logique générale du code, pas un simple accidentel "double libre". Une bonne façon de traiter le problème dans ce cas est de revoir et de repenser la structure du code, afin d'éviter la situation où le même pointeur est passé à free
plus d'une fois. Dans de tels cas, le réglage du pointeur à NULL
et en considérant le problème "résolu" n'est rien d'autre qu'une tentative de cacher le problème sous le tapis. Il ne fonctionnera tout simplement pas dans le cas général, parce que le problème avec la structure du code trouvera toujours une autre façon de se manifester.
enfin, si votre code est spécifiquement conçu pour s'appuyer sur la valeur du pointeur étant NULL
ou non NULL
, il est parfaitement correct de définir la valeur du pointeur à NULL
après free
. Mais en tant que règle générale de "bonne pratique" (comme dans "Toujours mettre votre pointeur sur NULL
après free
") il est, une fois de plus, un bien connu et assez inutile faux, souvent suivi par certains pour des raisons purement religieuses, vaudou-comme.
la plupart des réponses ont mis l'accent sur la prévention d'une double liberté, mais mettre le pointeur à NULL a un autre avantage. Une fois que vous avez libéré un pointeur, cette mémoire est disponible pour être réattribué par un autre appel à malloc. Si vous avez toujours le pointeur original autour de vous pourrait finir avec un bug où vous tentez d'utiliser le pointeur après libre et corrompu une autre variable, puis votre programme entre dans un état inconnu et toutes sortes de mauvaises choses peuvent se produire (plantage si vous êtes chanceux, données la corruption si vous êtes malchanceux). Si vous aviez défini le pointeur à NULL après libre, toute tentative de lire/écrire à travers ce pointeur plus tard aboutirait à une segfault, ce qui est généralement préférable à la corruption de mémoire aléatoire.
pour les deux raisons, ce peut être une bonne idée de mettre le pointeur à NULL après free(). Il 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 à NULL.
il s'agit d'une bonne pratique pour éviter de surcharger la mémoire. Dans la fonction ci-dessus, il est inutile, mais souvent quand c'est fait, il peut trouver des erreurs d'application.
Essayez quelque chose comme ceci à la place:
#if DEBUG_VERSION
void myfree(void **ptr)
{
free(*ptr);
*ptr = null;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = null; } while (0)
#endif
la version de débogage vous permet de profiler des frees dans le code de débogage, mais les deux sont fonctionnellement identiques.
Modifier : Ajout de la faire ... alors que, comme l'a suggéré ci-dessous, merci.
si vous atteignez pointer qui a été libre()d, il pourrait casser ou non. Cette mémoire pourrait être réallouée à une autre partie de votre programme et alors vous obtenez la corruption de mémoire,
si vous définissez le pointeur à NULL, alors si vous y accédez, le programme s'écrase toujours avec une segfault. Pas plus,, parfois ça marche", pas plus,, se brise d'une manière imprévisible". C'est beaucoup plus facile à déboguer.
mettre le pointeur sur la mémoire free
'D signifie que toute tentative d'accéder à cette mémoire par le pointeur se brisera immédiatement, au lieu de causer un comportement non défini. Il rend beaucoup plus facile de déterminer où les choses ont mal tourné.
je vois votre argument: puisque nPtr
sort de son champ d'application juste après nPtr = NULL
, il ne semble pas y avoir de raison de le fixer à NULL
. Toutefois, dans le cas d'un membre struct
ou quelque part sinon, si le pointeur ne sort pas immédiatement de la portée, cela a plus de sens. Il n'est pas immédiatement évident si oui ou non ce pointeur sera utilisé à nouveau par le code qui ne devrait pas l'utiliser.
il est probable que la règle est énoncée sans faire de distinction entre ces deux cas, parce qu'il est beaucoup plus difficile d'appliquer automatiquement la règle, encore moins pour les développeurs de la suivre. Il ne fait pas de mal de mettre des pointeurs à NULL
après chaque libre, mais il a le potentiel de relever de gros problèmes.
le bogue le plus commun en c est le double free. En gros, vous faites quelque chose comme ça
free(foobar);
/* lot of code */
free(foobar);
et ça finit plutôt mal, L'OS essaye de libérer de la mémoire déjà libérée et généralement il segfault. Donc, la bonne pratique est de mettre à NULL
, de sorte que vous pouvez faire des tests et vérifier si vous avez vraiment besoin de libérer cette mémoire
if(foobar != null){
free(foobar);
}
aussi à noter que free(NULL)
ne fera rien donc vous n'avez pas à écrire la déclaration si. Je Je ne suis pas vraiment un gourou OS mais je suis jolie même maintenant la plupart des os se crasheraient sur double free.
c'est aussi la raison principale pour laquelle toutes les langues avec collecte des déchets (Java, dotnet) étaient si fières de ne pas avoir ce problème et aussi de ne pas avoir à laisser aux développeurs la gestion de la mémoire dans son ensemble.
l'idée derrière ceci, est d'arrêter la réutilisation accidentelle du pointeur libéré.
ceci (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 vieux pointeur indiquerait maintenant un morceau valide de mémoire. Il est alors possible que quelqu'un utilise le pointeur, résultant en un État de programme invalide.
si vous annulez le pointeur, alors toute tentative de l'utiliser va déréférence 0x0 et planter là, ce qui est facile à déboguer. Aléatoire des pointeurs pointer vers une mémoire aléatoire est difficile à déboguer. Ce n'est évidemment pas nécessaire, mais c'est la raison pour laquelle c'est dans un document sur les pratiques exemplaires.
de la norme ANSI C:
void free(void *ptr);
la fonction libre provoque l'espace indiqué par ptr à être désallocalisé, c'est, mis à disposition pour de plus amples allocation. Si ptr est un pointeur nul, aucune action ne se produit. Sinon, si l' argument ne correspond pas à un pointeur plus tôt retourné par le calloc , fonction malloc, ou realloc, ou si l'espace a été libéré par un appel gratuit ou realloc , le comportement n'est pas défini.
"le comportement non défini" est presque toujours un plantage du programme. Pour éviter cela, il est sûr de réinitialiser le pointeur à NULL. free() lui-même ne peut pas faire ce qu'il est adopté seulement un pointeur, pas un pointeur vers un pointeur. Vous pouvez aussi écrire une version plus sûre de free () qui annule le pointeur:
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
je trouve que c'est peu d'aide car, d'après mon expérience, quand les gens accèdent à une allocation mémoire libérée, c'est presque toujours parce qu'ils ont un autre pointeur quelque part. Et puis il entre en conflit avec une autre norme de codage personnel qui est "éviter fouillis inutile", donc je ne le fais pas car je pense qu'il aide rarement et rend le code légèrement moins lisible.
Cependant - je ne vais pas définir la variable à null si le pointeur n'est pas censé être utilisé à nouveau, mais souvent le plus élevé le design de niveau me donne une raison de le régler à 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, alors le "contrat" si vous aimez de la classe est que ce membre pointera vers quelque chose de Valide à tout moment, donc il doit être défini à null pour cette raison. Une petite distinction, mais je pense important.
en c++ il est important de toujours penser qui possède ces données lorsque vous attribuez une certaine mémoire (à moins que vous utilisez des pointeurs intelligents mais même alors une certaine réflexion est nécessaire). Et ce processus tend à conduire à des pointeurs étant généralement un membre d'une certaine classe et généralement vous voulez qu'une classe soit dans un état valide à tout moment, et la façon la plus facile de faire cela est de mettre la variable membre à NULL pour l'indiquer pointe à rien maintenant.
un modèle commun est de définir tous les pointeurs de membre à NULL dans le constructeur et avoir l'appel de destructeur supprimer sur tous les pointeurs à des données qui votre dessin dit que la classe possède . Clairement, dans ce cas, vous devez définir le pointeur à NULL lorsque vous supprimez quelque chose pour indiquer que vous n'avez pas de données avant.
donc, pour résumer, Oui je mets souvent le pointeur à nul après avoir supprimé quelque chose, mais c'est dans le cadre d'une conception plus large et des réflexions sur qui possède les données plutôt que d'être due à 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 à faire ainsi et il ajoute "clutter" qui, dans mon expérience est tout aussi responsable des bogues et du mauvais code que ce genre de chose.
récemment, je suis tombé sur la même question après que je cherchais la réponse. Je suis arrivé à cette conclusion:
il s'agit d'une pratique exemplaire, 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 comme on change la plate-forme, donc vous ne devez pas vous attendre à ce qu'après avoir passé pointeur vers cette fonction et après avoir libéré de la mémoire, ce pointeur sera réglé à nul. Cela peut ne pas être le cas pour une bibliothèque implémentée pour la plate-forme.
il faut donc toujours aller pour
free(ptr);
ptr = NULL;
cette règle est utile quand vous essayez d'éviter les scénarios suivants:
1) Vous avez une fonction très longue avec une logique compliquée et une gestion de 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 faire valoir que le réglage à NULL peut masquer des erreurs de logique plus tard, ou dans le cas où vous supposez qu'il est valide, vous vous écrasez toujours sur NULL, donc cela n'a pas d'importance.
en général, je vous conseillerais de la mettre à zéro quand vous pensez que c'est une bonne idée, et ne vous embêtez pas quand vous pensez que ça n'en vaut pas la peine. Concentrer au lieu à l'écriture de courts fonctions et classes.
À ajouter à ce que d'autres ont dit, une bonne méthode d'utilisation du pointeur est toujours vérifier si il est un pointeur valide ou pas. Quelque chose comme:
if(ptr)
ptr->CallSomeMethod();
marquer explicitement le pointeur comme nul après le libérer permet ce type d'utilisation en C/C++.
ce pourrait être plus un argument pour initialiser tous les pointeurs à NULL, mais quelque chose comme ça peut être un bug 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();
}
p
finit au même endroit sur la pile que l'ancien nPtr
, donc il pourrait encore contenir un pointeur apparemment valide. Affecter à *p
pourrait écraser toutes sortes de choses sans rapport et conduire à des bogues laids. Surtout si le compilateur initialise des variables locales avec zéro en mode debug mais ne une fois les optimisations activées. Ainsi, les constructions de débogage ne montrent aucun signe de bogue pendant que les constructions de publication explosent au hasard...
définit le pointeur qui vient d'être libéré à nul n'est pas obligatoire mais une bonne pratique. De cette façon , vous pouvez éviter 1) en utilisant un point libéré 2)le libérer towice
paramétrages un pointeur vers NULL est destiné à protéger contre ce qu'on appelle la double-liberté-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 non défini - généralement tas de corruption ou de se planter immédiatement le programme. Appeler free () pour un pointeur nul ne fait rien et est donc garanti pour être sûr.
donc la meilleure pratique à moins que vous ne soyez sûr que le pointeur quitte scope immédiatement ou très peu de temps après free() est de mettre ce pointeur à NULL de sorte que même si free() est appelé à nouveau, il est maintenant appelé pour un pointeur NULL et un comportement non défini est éludé.
il y a deux raisons:
éviter les accidents en cas de double libération
Écrit par RageZ dans un double question .
le bogue le plus commun en c est le double gratuit. Fondamentalement, vous faites quelque chose comme que
free(foobar); /* lot of code */ free(foobar);
et il se termine assez mauvais, L'OS essayer pour libérer une mémoire déjà libérée et généralement, c'erreur de segmentation. Donc, la bonne la pratique est de mettre à
NULL
, donc vous peut faire le test et vérifier si vous besoin de libérer cette mémoireif(foobar != null){ free(foobar); }
à noter également que
free(NULL)
ne rien faire si vous n'avez pas à Ecrivez la déclaration if. Je ne suis pas vraiment un OS gourou mais je suis assez Pair maintenant, la plupart des os s'écraseraient sur le double gratuit.C'est aussi une raison principale pour laquelle tout langues avec les ordures collection (Java, dotnet) était si fier de ne pas avoir ce problème et aussi pas avoir à quitter pour le développeur gestion de la mémoire dans son ensemble.
éviter d'utiliser des pointeurs déjà libérés
écrit par Martin C. Löwis in a another answer .
définir les pointeurs inutilisés à NULL est un style défensif, protéger contre des insectes pointeurs pendants. Si un bancales pointeur est accessible après il est libéré, vous pouvez lire ou remplacer aléatoire mémoire. Si un pointeur nul est accédé, vous obtenez un accident immédiat sur la plupart systèmes, vous dire tout de suite ce que l'erreur est.
Pour les variables locales, il peut être un peu inutile si elle est "évident" que le pointeur n'est pas accédée après avoir été libérée, donc ce style est plus approprié pour des données sur les membres et mondial variable. Même pour les variables locales, il peut être une bonne approche si la fonction continue après la mémoire est libérée.
pour compléter le style, vous devriez également initialiser les pointeurs à NULL avant on leur attribue un vrai pointeur valeur.
l'idée est que si vous essayez de déréférencer le pointeur non-plus-valide après l'avoir libéré, vous voulez échouer dur (segfault) plutôt que silencieusement et mystérieusement.
mais... être prudent. Tous les systèmes ne provoquent pas de segfault si vous déréférenciez NULL. On (au moins certaines versions de) AIX, *(int *) 0 == 0, et Solaris a une compatibilité optionnelle avec cette fonctionnalité AIX"."
à la question initiale:
Définir le pointeur à 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, est entièrement débogué et ne sera plus jamais modifié. D'un autre côté, annuler défensivement un pointeur qui a été libéré peut être très utile quand quelqu'un ajoute sans réfléchir un nouveau bloc de code sous le free(), quand la conception du module original n'est pas correcte, et dans le cas de il-compile-mais-ne-fait-ce-que-je-veux des bestioles.
dans n'importe quel système, il y a un but impossible de le rendre plus facile à la bonne chose, et le coût irréductible de mesures inexactes. En C, on nous offre un ensemble d'outils très pointus et très puissants, qui peuvent créer beaucoup de choses dans les mains d'un travailleur qualifié, et infliger toutes sortes de blessures métaphoriques lorsqu'on les manipule mal. Certains sont difficiles à comprendre ou à utiliser correctement. Et les gens, être naturellement le risque averse, faire des choses irrationnelles comme vérifier un pointeur pour la valeur nulle avant d'appeler libre avec elle…
le problème de mesure est que chaque fois que vous tentez de diviser bon de moins bon, plus le cas complexe, plus vous obtenez une mesure ambiguë. Si l'objectif est de ne garder que les bonnes pratiques, alors certaines sont ambiguës et sont rejetées avec des pratiques qui ne sont pas bonnes. SI votre objectif est d'éliminer le pas bon, alors le les ambiguïtés peuvent rester avec la bonne. Les deux buts, garder seulement bon ou éliminer clairement mauvais, semblerait être diamétralement opposé, mais il ya habituellement un troisième groupe qui est ni l'un ni l'autre, certains des deux.
avant de faire un cas avec le département Qualité, essayez de regarder à travers la base de données de bogue pour voir combien de fois, si jamais, les valeurs de pointeur invalides ont causé des problèmes qui ont dû être écrits. Si vous voulez faire une réelle différence, identifiez le problème le plus courant dans votre code de production et de proposer trois façons de le prévenir
comme vous avez une équipe d'assurance de la qualité en place, permettez-moi d'ajouter un point mineur au sujet de L'assurance de la qualité. Certains outils D'assurance de la qualité automatisés pour C signaleront les assignations à des pointeurs libérés comme "assignation 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 sélectivement les messages, de sorte que vous pouvez toujours satisfaire aux deux exigences QA, si votre équipe en décide ainsi.
il est toujours conseillé de déclarer une variable d'indicateur avec NULL tel que,
int *ptr = NULL;
disons, ptr pointe vers 0x1000 adresse mémoire.
Après l'utilisation de free(ptr)
, il est toujours conseillé d'annuler la variable de pointeur en déclarant à nouveau à NULL .
par exemple:
free(ptr);
ptr = NULL;
Si pas de nouveau déclaré NULL , la variable pointeur continue de pointer vers la même adresse ( 0x1000 ), cette variable pointeur est appelé un pointeur pendulaire . Si vous définissez une autre variable de pointeur (disons, q ) et attribuez dynamiquement l'adresse au nouveau pointeur, il y a une chance de prendre la même adresse ( 0x1000 ) par nouvelle variable de pointeur. Si dans le cas, vous utilisez le même pointeur ( ptr ) et mettre à jour la valeur à l'adresse pointée par le pointeur de même ( ptr ), puis le programme va finir l'écriture d'une valeur à l'endroit où q pointe (depuis p et q pointent vers la même adresse ( 0x1000 )).
p.ex.
*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.
pour faire court: 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 permettez que cette adresse dans le tas soit attribuée à une autre application.
cependant, si vous ne définissez pas le pointeur à NULL, et par erreur essayer de dé-référence le pointeur, ou changer la valeur de cette adresse, vous pouvez encore le faire. MAIS PAS QUELQUE CHOSE QUE TU VOUDRAIS LOGIQUEMENT FAIRE.
Pourquoi ne puis-je toujours accéder à l'emplacement de mémoire que j'ai libéré? Parce que: vous pouvez avoir la mémoire libre, mais la variable pointeur avait encore des informations sur l'adresse mémoire tas. Donc, en tant que stratégie défensive, mettez-la à zéro.