Question

Dans mon entreprise, il existe une règle de codage qui dit qu'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;
}

J’ai le sentiment 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 pas de sens dans de tels cas, je vais en parler avec "l'équipe qualité". supprimer cette règle de codage. S'il vous plaît des conseils.

Était-ce utile?

La solution

Définir la valeur NULL sur les pointeurs inutilisés 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 l’erreur.

Pour les variables locales, cela peut être un peu inutile s'il est "évident" que le pointeur n’est plus accessible après la libération, ce style convient donc mieux aux données membres et aux 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.

Autres conseils

Définir un pointeur sur NULL après free est une pratique douteuse qui est souvent popularisée en tant que "bonne programmation". statuer sur une prémisse fausse. C’est l’une de ces fausses vérités qui appartiennent au "ça sonne bien" mais ne réalisent en réalité absolument rien d’utile (et ont parfois des conséquences négatives).

Apparemment, définir un pointeur sur NULL après que free est censé empêcher le redouté "double free". problème lorsque la même valeur de pointeur est transmise à free plusieurs fois. En réalité cependant, dans 9 cas sur 10, le véritable "double libre" Ce problème se produit lorsque des objets différents contenant la même valeur sont utilisés comme arguments pour free . Inutile de dire que définir un pointeur sur NULL après que free ne réalise absolument rien pour empêcher le problème dans de tels cas.

Bien sûr, il est possible de tomber sur "double libre". problème lorsqu’on utilise 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 ce cas, définissez le pointeur sur NULL et tenez compte du problème "résolu". n’est rien d’autre 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 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 en règle générale, "bonne pratique" La règle (comme dans "placez toujours votre pointeur sur NULL après free ") est, encore une fois, un faux bien connu et assez inutile, souvent suivi de pour des raisons purement religieuses, de type vaudou.

La plupart des réponses ont été axées sur la prévention de la 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 mauvaises choses 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 de lecture / écriture par le biais de ce pointeur entraînerait une erreur de segmentation, 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’ya aucune 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 n’est pas nécessaire, 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 en mode de débogage, mais leur fonctionnement est identique.

Modifier : Ajoute do ... tandis que, comme suggéré ci-dessous, merci.

Si vous atteignez un pointeur qui a été libéré () d, il risque de se briser ou non. Cette mémoire peut être réaffectée à une autre partie de votre programme et vous obtiendrez 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, ça plante de façon imprévisible ''. C'est beaucoup plus facile à déboguer.

Si vous définissez le pointeur sur la mémoire free , cela signifie que toute tentative d'accès à cette mémoire via le pointeur se bloquera 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: étant donné que nPtr est hors de portée juste après nPtr = NULL , il ne semble pas y avoir de raison de le définir sur < code> NULL . Cependant, dans le cas d'un membre struct ou d'un autre endroit où le pointeur ne sort pas immédiatement de la 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, et encore moins aux développeurs de la suivre. Cela ne fait pas de mal de définir les pointeurs sur NULL après chaque libération, mais cela a le potentiel de signaler de gros problèmes.

le bogue le plus courant dans c est le double libre. Fondamentalement, vous faites quelque chose comme ça

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

et ça finit pas mal, le système d’exploitation essaye 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 la valeur sur NULL afin de pouvoir effectuer un test et vérifier si vous devez réellement libérer cette mémoire

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

Il faut é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 également une des principales raisons 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 est de mettre fin à la 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 invalide.

Si vous annulez la valeur du pointeur, 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 il figure dans un document sur les meilleures pratiques.

À partir de la norme ANSI C:

void free(void *ptr);
  

La fonction free provoque l’espace   pointé par ptr pour être désalloué,   c’est-à-dire mis à disposition pour   allocation. Si ptr est un pointeur nul,   aucune action ne se produit. Sinon, si le   l'argument ne correspond pas à un pointeur   retourné plus tôt par le calloc,   malloc, ou fonction realloc, ou si   l'espace a été libéré par un   appelez free ou realloc, le comportement   n'est pas défini.

" le comportement non défini " est presque toujours un crash de 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;
}

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 vers elle quelque part. 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 je pense 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. Par exemple, si le pointeur est un membre d'une classe et que j'ai supprimé ce à quoi il pointe, alors le "contrat" si vous aimez la classe, c'est que ce membre indiquera 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 savoir qui détient ces données lorsque vous allouez de la mémoire (à moins que vous utilisiez des pointeurs intelligents, mais que, dans ce cas, certaines réflexions s'imposent). 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 de membre sur NULL dans le constructeur et de faire en sorte que l'appel du destructeur soit supprimé de tous les pointeurs vers des données dont la conception indique que la classe est propriétaire . 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 donne une idée de qui est 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’ya aucun avantage à le faire et cela ajoute "fouillis". qui, selon mon expérience, est tout aussi responsable des bugs et des codes défectueux que ce genre de chose.

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

C’est une bonne 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 sur cette fonction et après avoir libéré de la mémoire, ce pointeur sera défini sur NULL. . Ce n'est peut-être pas le cas pour certaines bibliothèques implémentées pour la plate-forme.

alors allez toujours pour

free(ptr);
ptr = NULL;

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

1) Vous avez une très longue fonction 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 plus tard dans la fonction.

2) Le pointeur est une variable membre d'une classe dont le comportement est assez complexe et vous ne souhaitez pas le réutiliser accidentellement 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 s'allongeait, cela pourrait avoir de l'importance.

Vous pouvez faire valoir que le réglage sur NULL peut en réalité masquer des erreurs de logique ultérieurement, ou dans le cas où vous supposez que le code 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 fonctions courtes et de cours bien conçus.

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();

Marquer explicitement le pointeur comme NULL après l'avoir libéré autorise ce type d'utilisation en C / C ++.

Cela pourrait être davantage un argument pour initialiser tous les pointeurs sur NULL, mais cela 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 se retrouve 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 non liées 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. Ainsi, les versions de débogage ne montrent aucun signe du bogue tandis que les versions des 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

Paramètres Un pointeur sur NULL sert à 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.

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.

Donc, la meilleure pratique à moins que vous ne soyez sûr que le pointeur quitte le champ immédiatement ou très peu de temps après free () consiste à 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 indéfini est éludé.

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 ... soyez prudent. 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 est optionnellement compatible avec cette fonctionnalité AIX.

À la question initiale: Définir 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, l'objectif est inaccessible: 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 à 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 du bien, plus le cas est complexe, plus vous obtenez 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 traiter avec le 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-lui trois solutions pour l’empêcher

Il y a deux raisons:

Évitez les collisions lors de la libération double

Rédigé par RageZ dans un question en double .

  

Le bogue le plus courant dans c est le double   libre. Fondamentalement, vous faites quelque chose comme   que

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

et ça finit pas mal, le système d’essai essaie   pour libérer de la mémoire déjà libérée et   généralement il segfault. Donc le bon   pratique consiste à définir sur NULL , de sorte que vous   peut faire des tests et vérifier si vous avez vraiment   besoin de libérer cette mémoire

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

à noter également que free (NULL)   ne fera rien pour que vous n'ayez pas à   écrivez la déclaration si. je ne suis pas   vraiment un gourou OS mais je suis assez même   maintenant, la plupart des systèmes d'exploitation planteraient en double   gratuit.

     

C’est aussi une des principales raisons pour lesquelles tous   langues avec ramasse-miettes   (Java, dotnet) était si fier de ne pas   avoir ce problème et aussi pas   avoir à quitter pour développer le   gestion de la mémoire dans son ensemble.

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

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

  

Définir les pointeurs inutilisés sur NULL est un   style défensif, protégeant contre   bogues pointeurs. Si un balançant   on accède au pointeur après sa libération,   vous pouvez lire ou écraser au hasard   Mémoire. Si un pointeur nul est accédé,   vous obtenez un crash immédiat sur la plupart   systèmes, vous dire tout de suite ce que   l'erreur est.

     

Pour les variables locales, cela peut être un   peu inutile si c'est   " évident " que le pointeur n'est pas   accédé plus après avoir été libéré, donc   ce style est plus approprié pour   données membres et variables globales. Même   pour les variables locales, cela peut être un bon   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   ils se voient attribuer un vrai pointeur   valeur.

Comme vous avez une équipe d’assurance qualité en place, permettez-moi d’ajouter un point mineur sur l’assurance qualité. Certains outils d’assurance qualité automatisés pour C marqueront les affectations aux pointeurs libérés comme des "attributions inutiles à ptr ". Par exemple, PC-lint / FlexeLint de Gimpel Software dit tst.c 8 Avertissement 438: La dernière valeur affectée à la variable 'nPtr' (définie à la ligne 5) n'a pas été utilisée

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.

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

.
int *ptr = NULL;

Supposons que ptr pointe sur une adresse mémoire 0x1000 . Après avoir utilisé free (ptr) , il est toujours conseillé d’annuler la variable de pointeur en déclarant de nouveau la valeur NULL . par exemple:

free(ptr);
ptr = NULL;

Si elle n'est pas à nouveau déclarée dans NULL , la variable de pointeur continue de pointer vers la même adresse ( 0x1000 ), cette variable de pointeur s'appelle une pendaison. pointeur . Si vous définissez une autre variable de pointeur ( q , par exemple) et allouez dynamiquement une adresse au nouveau pointeur, vous pouvez utiliser la même adresse ( 0x1000 ). variable. Si 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'emplacement où pointe q (car p et q pointent 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.

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 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 de la mémoire de tas. Donc, en tant que stratégie défensive, définissez-le sur NULL.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top