Dans mon entreprise, il existe une règle de codage selon laquelle, après avoir libéré de la mémoire, réinitialisez 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 présenté ci-dessus, la définition de NULL n'a aucune signification. Ou est-ce que je manque quelque chose?
S'il n'y a aucun sens dans de tels cas, je vais en parler à "l'équipe qualité" pour supprimer cette règle de codage. S'il vous plaît des conseils.
Définir les pointeurs inutilisés sur NULL est un style défensif, protégeant contre les bogues de pointeurs pendantes. Si vous accédez à un pointeur suspendu après sa libération, vous pouvez lire ou écraser la mémoire aléatoire. Si vous accédez à un pointeur null, 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 ne soit plus accessible après la libération, ce style est donc 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 compléter le style, vous devez également initialiser les pointeurs sur NULL avant qu'ils ne se voient attribuer une valeur de pointeur vraie.
Définir un pointeur sur NULL
après free
est une pratique douteuse qui est souvent popularisée sous le nom de 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 justes" mais qui en réalité ne produisent absolument rien d'utile (et aboutissent parfois à des conséquences négatives).
Le fait de définir un pointeur sur NULL
après free
est censé empêcher le problème redouté de "double libération" lorsque la même valeur de pointeur est transmise à free
plusieurs fois. En réalité cependant, dans 9 cas sur 10, le véritable problème de "double libération" se produit lorsque différent des objets pointeur contenant la même valeur de pointeur sont utilisés comme arguments pour free
. Inutile de dire que placer un pointeur sur NULL
après free
ne permet absolument pas de prévenir le problème dans de tels cas.
Bien sûr, il est possible de rencontrer le problème "double libre" en utilisant le même objet pointeur comme argument de free
. Cependant, dans la réalité, de telles situations indiquent normalement un problème avec la structure logique générale du code et non un simple "double libre" accidentel. Une façon appropriée de traiter le problème dans de tels cas consiste à revoir et à 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, placer le pointeur sur NULL
et considérer le problème comme étant "corrigé" n’est rien de plus qu’une tentative de passer 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 de code trouvera toujours un autre moyen de se manifester.
Enfin, si votre code est spécifiquement conçu pour que la valeur du pointeur soit NULL
ou non NULL
, il est parfaitement correct de définir la valeur du pointeur sur NULL
après free
. Mais comme règle générale de "bonne pratique" (comme dans "placez toujours votre pointeur sur NULL
aprè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, comme le vaudou.
La plupart des réponses se sont concentrées sur la prévention d'une double libération, mais le fait de positionner le pointeur sur NULL présente un autre avantage. Une fois que vous avez libéré un pointeur, cette mémoire est disponible pour être réaffectée par un autre appel à malloc. Si vous avez toujours le pointeur d'origine autour de vous, vous risquez de vous retrouver avec un bogue dans lequel vous essayez d'utiliser le pointeur après free et de corrompre une autre variable, puis votre programme entre dans un état inconnu et toutes sortes de problèmes peuvent se produire (blocage êtes chanceux, corruption de données si vous êtes malchanceux). Si vous aviez défini le pointeur sur NULL après la libération, toute tentative ultérieure de lecture/écriture via ce pointeur entraînerait un segfault, ce qui est généralement préférable à la 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 de pointeur sort de la portée immédiatement après free (), il n'y a pas de raison de la définir sur NULL.
Ceci est considéré comme une bonne pratique pour éviter d'écraser la mémoire. Dans la fonction ci-dessus, cela est inutile, mais souvent, lorsque cela 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
DEBUG_VERSION vous permet de profiler libère dans le code de débogage, mais les deux sont fonctionnellement identiques.
Edit : Ajoute do ... alors que comme suggéré ci-dessous, merci.
Définir le pointeur sur la mémoire free
'd signifie que toute tentative d’accéder à cette mémoire par le biais du pointeur plantera immédiatement au lieu de provoquer un comportement indéfini. Il est beaucoup plus facile de déterminer où les choses se sont mal passées.
Je peux voir votre argument: puisque nPtr
sort du champ juste après nPtr = NULL
, il ne semble pas y avoir de raison de le définir sur NULL
. Cependant, dans le cas d'un membre struct
ou d'un autre endroit où le pointeur ne sort pas immédiatement de sa portée, cela a plus de sens. Il n'est pas immédiatement évident de savoir 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, sans parler du fait que les développeurs doivent la suivre. Cela ne fait pas de mal de régler les pointeurs sur NULL
après chaque version libre, mais cela a le potentiel de signaler de gros problèmes.
Si vous atteignez un pointeur qui a été libre () d, il risque de se briser ou non. Cette mémoire peut être réaffectée à une autre partie de votre programme et vous obtenez alors une corruption de mémoire,
Si vous définissez le pointeur sur NULL, le programme se bloque toujours avec une erreur de segmentation si vous y accédez. Pas plus, parfois ça marche '', pas plus, plante de façon imprévisible ''. C'est beaucoup plus facile à déboguer.
le bogue le plus courant dans c est le double gratuit. Fondamentalement, vous faites quelque chose comme ça
free(foobar);
/* lot of code */
free(foobar);
et cela finit mal, le système d’exploitation essaie de libérer de la mémoire déjà libérée et en général il segfault. La bonne pratique consiste donc à définir sur NULL
afin de pouvoir effectuer un test et vérifier si vous avez réellement besoin de libérer cette mémoire.
if(foobar != null){
free(foobar);
}
il est également à noter que free(NULL)
ne fera rien, vous n'avez donc pas à écrire l'instruction if. Je ne suis pas vraiment un gourou des systèmes d'exploitation, mais je suis jolie même maintenant que la plupart des systèmes d'exploitation planteraient en double libre.
C'est aussi une des raisons principales pour lesquelles toutes les langues avec garbage collection (Java, dotnet) étaient si fières 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.
L'idée sous-jacente est d'empêcher toute réutilisation accidentelle du pointeur libéré.
Ceci (peut) être réellement important. Bien que vous libériez de la mémoire, une partie ultérieure du programme pourrait allouer quelque chose de nouveau pour atterrir dans l'espace. Votre ancien pointeur indiquerait maintenant un bloc de mémoire valide. Il est alors possible que quelqu'un utilise le pointeur, ce qui entraîne un état de programme non valide.
Si vous supprimez le pointeur NULL, toute tentative d'utilisation de ce dernier va déréférencer 0x0 et se bloquer, 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 la raison pour laquelle cela figure dans un document sur les meilleures pratiques.
Je trouve cela peu d’aide car, selon mon expérience, lorsque des personnes accèdent à une allocation de mémoire libérée, c’est presque toujours parce qu’elles ont un autre pointeur dessus. Et puis, cela entre en conflit avec une autre norme de codage personnelle, qui est "Évitez les encombrements inutiles", aussi je ne le fais pas, car j'estime que cela aide rarement et rend le code légèrement moins lisible.
Cependant, je ne définirai pas la variable sur null si le pointeur n'est pas censé être utilisé à nouveau, mais la conception de niveau supérieur me donne souvent 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, alors le "contrat" si vous aimez de la classe est que ce membre pointe sur quelque chose de valide à tout moment, il doit donc être mis à null pour cette raison. Une petite distinction mais je pense une importante.
En c ++, il est important de toujours penser à qui possède ces données lorsque vous allouez de la mémoire (sauf si vous utilisez des pointeurs intelligents, mais que, dans ce cas, une certaine réflexion soit nécessaire). Et ce processus tend généralement à faire en sorte que les pointeurs soient généralement membres d'une classe et que vous souhaitiez généralement qu'une classe soit toujours dans un état valide. La meilleure façon de le faire est de définir la variable membre sur NULL pour l'indiquer. à rien maintenant.
Un modèle courant consiste à définir tous les pointeurs membres sur NULL dans le constructeur et à faire en sorte que l'appel du destructeur soit supprimé de tous les pointeurs vers les données pour lesquelles 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 place souvent le pointeur sur NULL après la suppression de quelque chose, mais cela fait partie d'une conception plus large et d'une réflexion sur le propriétaire des données plutôt que sur le fait 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 un "fouillis" qui, selon mon expérience, est tout aussi responsable des bugs et des codes défectueux que ce genre de chose.
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 qu'il est disponible 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 est indéfini.
"le comportement indéfini" est presque toujours un blocage du programme. Pour éviter cela, il est prudent de réinitialiser le pointeur sur NULL. free () lui-même ne peut pas le faire car il ne transmet qu'un pointeur, pas un pointeur à un pointeur. Vous pouvez également écrire une version plus sûre de free () qui NULLs le pointeur:
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
Récemment, je suis tombé sur la même question après avoir recherché la réponse. Je suis arrivé à cette conclusion:
C'est la meilleure pratique, et il faut suivre ceci pour le rendre portable sur tous les systèmes (embarqués).
free()
est une fonction de bibliothèque, qui varie selon les changements de plate-forme. Vous ne devez donc pas vous attendre à ce que, après avoir passé le pointeur à cette fonction et après avoir libéré de la mémoire, ce pointeur soit défini sur NULL. Cela peut ne pas être le cas pour certaines bibliothèques implémentées pour la plate-forme.
alors toujours aller pour
free(ptr);
ptr = NULL;
Cette règle est utile lorsque 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 la mémoire et vous ne voulez pas réutiliser accidentellement le pointeur sur la mémoire supprimée ultérieurement dans la fonction.
2) Le pointeur est une variable membre d'une classe dont le comportement est assez complexe et vous ne voulez pas réutiliser accidentellement le pointeur pour effacer de la mémoire 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 fait de le définir sur NULL peut en réalité masquer des erreurs de logique ultérieurement, ou que, dans le cas où vous supposez que le document est valide, vous plantez toujours sur NULL, ce qui importe peu.
En général, je vous conseillerais de définir la valeur sur NULL lorsque vous pensez que c'est une bonne idée et de ne pas vous déranger si vous pensez que cela n'en vaut pas la peine. Concentrez-vous plutôt sur l'écriture de courtes fonctions et de cours bien conçus.
Il y a deux raisons:
Écrit par RageZ dans un question dupliquée .
Le bogue le plus courant dans c est le double gratuit. Fondamentalement, vous faites quelque chose comme ça
free(foobar); /* lot of code */ free(foobar);
et cela finit mal, le système d’exploitation essaie de libérer de la mémoire déjà libérée et en général il segfault. La bonne pratique consiste donc à définir sur
NULL
afin de pouvoir effectuer un test et vérifier si vous avez réellement besoin de libérer cette mémoire.if(foobar != NULL){ free(foobar); }
il est également à noter que
free(NULL)
ne fera rien, vous n'avez donc pas à écrire l'instruction if. Je ne suis pas vraiment un gourou des systèmes d'exploitation, mais je suis jolie même maintenant que la plupart des systèmes d'exploitation planteraient en double libre.C'est aussi une des raisons principales pour lesquelles toutes les langues avec garbage collection (Java, dotnet) étaient si fières de ne pas avoir ce problème et de ne pas avoir à laisser au développeur la gestion de la mémoire dans son ensemble.
Écrit par Martin v. Löwis dans un autre réponse .
Définir les pointeurs inutilisés sur NULL est un style défensif qui protège contre les bogues pointeurs. Si vous accédez à un pointeur suspendu après sa libération, vous pouvez lire ou écraser la mémoire aléatoire. Si vous accédez à un pointeur null, 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 ne soit plus accessible après la libération, ce style est donc 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 compléter le style, vous devez également initialiser les pointeurs sur NULL avant qu'ils ne se voient attribuer une valeur de pointeur vraie.
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 durement (erreur de segmentation) plutôt que silencieusement et mystérieusement.
Mais fais attention. Tous les systèmes ne provoquent pas un segfault si vous déréférenciez NULL. Sous (au moins certaines versions) d’AIX, * (int *) 0 == 0, et Solaris offre une compatibilité facultative avec cette "fonctionnalité" AIX.
Pour ajouter à ce que d'autres ont dit, une bonne méthode d'utilisation du pointeur consiste à toujours vérifier s'il s'agit d'un pointeur valide ou non. Quelque chose comme:
if(ptr)
ptr->CallSomeMethod();
Marquer explicitement le pointeur comme NULL après l'avoir libéré permet ce type d'utilisation en C/C++.
Cela pourrait être plus un argument pour initialiser tous les pointeurs sur NULL, mais quelque chose comme ceci 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();
}
p
finit au même endroit sur la pile que l'ancien nPtr
, de sorte qu'il peut toujours contenir un pointeur apparemment valide. Assigner à *p
pourrait écraser toutes sortes de choses sans rapport et conduire à de vilains bugs. Surtout si le compilateur initialise les variables locales avec zéro en mode débogage mais ne le fait pas une fois les optimisations activées. Donc, les versions de débogage ne montrent aucun signe du bogue tandis que les versions de versions explosent de manière aléatoire ...
Définir le pointeur qui vient d'être libéré sur NULL n'est pas obligatoire, mais constitue une bonne pratique. De cette façon, vous pouvez éviter 1) l’utilisation d’un pointu libre 2) le libérer
Un pointeur sur NULL est destiné à protéger un soi-disant double libre - une situation dans laquelle free () est appelé plusieurs fois pour la même adresse sans réaffecter le bloc à cette adresse.
Un double libre entraîne un comportement indéfini - généralement une corruption de tas ou un crash immédiat du programme. Appeler free () pour un pointeur NULL ne fait rien et la sécurité est donc garantie.
Par conséquent, la meilleure pratique à moins que vous sachiez maintenant que le pointeur quitte le champ immédiatement ou très peu de temps après free () consiste à définir ce pointeur sur NULL de sorte que même si free () est appelé à nouveau, il est maintenant appelé pour un pointeur NULL et un comportement indéfini. est évité.
A la question initiale: Régler le pointeur sur NULL directement après la libération du contenu est une perte de temps totale, à condition que le code réponde à toutes les exigences, qu'il soit entièrement débogué et qu'il ne soit plus jamais modifié. NULLer de manière défensive 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 contraire, -compile-mais-ne-fait pas-ce-que-je-veux des bugs.
Quel que soit le système, il existe un objectif impossible à atteindre qui consiste à simplifier les choses, et le coût irréductible de mesures inexactes. En C, on nous propose un ensemble d’outils très tranchants, 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 manipulés incorrectement. Certains sont difficiles à comprendre ou à utiliser correctement. Et les gens, étant naturellement peu enclins à prendre des risques, font des choses irrationnelles comme vérifier un pointeur pour une valeur NULL avant d'appeler gratuitement avec lui…
Le problème de la mesure est que, chaque fois que vous tentez de séparer le bien du moins du bien, plus le cas est complexe, plus vous obtiendrez une mesure ambiguë. Si l'objectif est de ne garder que les bonnes pratiques, alors certaines ambiguës sont jetées avec ce qui n'est vraiment pas bon. Si votre objectif est d’éliminer ce qui n’est pas bon, les ambiguïtés peuvent rester avec le bien. Les deux objectifs, ne garder que le bien ou éliminer clairement le mal, sembleraient être diamétralement opposés, mais il existe généralement un troisième groupe qui n'est ni l'un ni l'autre, certains des deux.
Avant de créer un dossier auprès du service qualité, essayez de parcourir la base de données de bogues pour voir à quelle fréquence, le cas échéant, des valeurs de pointeur invalides entraînaient des problèmes qui devaient être consignés. Si vous voulez vraiment faire la différence, identifiez le problème le plus courant dans votre code de production et proposez trois moyens de le prévenir.
Il est toujours conseillé de déclarer une variable de pointeur avec NULL tel que,
int *ptr = NULL;
Disons que ptr pointe sur x10 adresse mémoire. Après avoir utilisé free(ptr)
, il est toujours conseillé d’annuler la variable de pointeur en déclarant à nouveau à NULL . par exemple.:
free(ptr);
ptr = NULL;
Si ce n'est pas re-déclaré à NULL , la variable de pointeur continue de pointer vers la même adresse (x10), cette variable de pointeur est appelé un pointeur en suspens. Si vous définissez une autre variable de pointeur (par exemple, q) et allouez dynamiquement une adresse au nouveau pointeur, vous pouvez utiliser la même adresse (x10) avec un nouveau pointeur. variable. Si dans ce cas, vous utilisez le même pointeur (ptr) et mettez à jour la valeur à l'adresse indiquée par le même pointeur (ptr), le programme finira par écrire un valeur à l'endroit où q pointe (puisque p et q pointent vers la même adresse (x10) ).
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.
Longue histoire: vous ne voulez pas accéder accidentellement (par erreur) à l'adresse que vous avez libérée. En effet, lorsque vous libérez l'adresse, vous autorisez cette adresse du tas à être allouée à une autre application.
Toutefois, si vous ne définissez pas le pointeur sur NULL et tentez par erreur de dé-référencer le pointeur ou de modifier la valeur de cette adresse; VOUS POUVEZ ENCORE LE FAIRE. MAIS PAS QUELQUE CHOSE QUE VOUS VOULEZ LOGIQUEMENT FAIRE.
Pourquoi puis-je toujours accéder à l'emplacement de mémoire que j'ai libéré? Parce que: Vous avez peut-être libéré de la mémoire, mais la variable pointeur contenait toujours des informations sur l'adresse de la mémoire de tas. Donc, en tant que stratégie défensive, définissez-le sur NULL.
Comme vous avez une équipe d’assurance qualité en place, permettez-moi d’ajouter un point mineur au sujet de l’AQ. Certains outils d’assurance qualité automatisés pour C marqueront les affectations aux pointeurs libérés comme "attribution inutile à ptr
". Par exemple, PC-lint/FlexeLint de Gimpel Software indique 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 puissiez toujours satisfaire aux deux exigences de contrôle qualité, si votre équipe en décide ainsi.