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 NULL
n'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.
c
coding-style
malloc
free
heap-memory
Alphaneo
la source
la source
ptr == NULL
avant d'en faire quoi que ce soit. Si vous n'annulez pas vos pointeurs gratuits, vous obtiendrez unptr != NULL
pointeur inutilisable.Réponses:
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.
la source
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.Définir un pointeur vers
NULL
aprèsfree
est 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
NULL
afterfree
est censé empêcher le redoutable problème de "double free" lorsque la même valeur de pointeur est transmise àfree
plusieurs 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 pourfree
. Inutile de dire que définir un pointeur surNULL
aprèsfree
n'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é àfree
plusieurs reprises. Dans de tels cas, placer le pointeur surNULL
et 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
NULL
ou nonNULL
, il est parfaitement normal de définir la valeur du pointeurNULL
aprèsfree
. Mais en tant que règle générale de «bonne pratique» (comme dans «toujours placer votre pointeur surNULL
aprèsfree
»), 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.la source
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
. Ici, la misebar
àNULL
après l'appel àfree
la fonction va penser qu'il n'a jamais eu un bar et retourner la valeur faux!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.
la source
free
, mais cela a du sens.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:
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.
la source
do { } while(0)
bloc pourif(x) myfree(x); else dostuff();
ne pas casser.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.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.
la source
Définir le pointeur sur la
free
mé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
nPtr
sort juste aprèsnPtr = NULL
, il ne semble pas y avoir de raison de le définirNULL
. Cependant, dans le cas d'unstruct
membre 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
NULL
après chaque gratuit, mais cela a le potentiel de signaler de gros problèmes.la source
le bogue le plus courant en c est le double free. En gros, tu fais quelque chose comme ça
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émoireNotez é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.
la source
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 }
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écessiteraitfree(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 pointeursNULL
, cela ne devrait pas leur être imposé.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 avecsizeof
").L'idée derrière cela, est d'arrêter la réutilisation accidentelle du pointeur libéré.
la source
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.
la source
De la norme ANSI C:
"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:
la source
NULL
pour éviter de masquer les erreurs. stackoverflow.com/questions/1025589/... Il semble que dans les deux cas, certaines erreurs soient masquées.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.
la source
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
la source
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.
la source
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:
Le marquage explicite du pointeur comme NULL après sa libération permet ce type d'utilisation en C / C ++.
la source
Cela pourrait être plus un argument pour initialiser tous les pointeurs à NULL, mais quelque chose comme ça peut être un bogue très sournois:
p
se termine au même endroit sur la pile que l'anciennPtr
, il peut donc contenir un pointeur apparemment valide. Assigner à*p
pourrait é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 ...la source
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
la source
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é.
la source
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.
la source
À 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
la source
Il y a deux raisons:
Évitez les plantages lors de la double libération
Écrit par RageZ dans une question en double .
Évitez d'utiliser des pointeurs déjà libérés
Écrit par Martin c. Löwis dans une autre réponse .
la source
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 dittst.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.
la source
Il est toujours conseillé de déclarer une variable de pointeur avec NULL telle que,
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: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
la source
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.
la source