48 votes

Doit-on vraiment définir des pointeurs vers `NULL`, après leur libération?

Il semble y avoir deux arguments pourquoi on devrait définir un pointeur vers NULL après leur libération.

Éviter de s'écraser lors de la double libération de pointeurs.

Bref: l'Appel d' free() un deuxième temps, par accident, ne tombe pas en panne quand il est réglé sur NULL.

  • Presque toujours, cela masque une logique bug car il n'y a pas de raison d'appeler à l' free() un deuxième temps. Il est plus sûr de laisser le crash de l'application et être en mesure de le réparer.

  • Il n'est pas garanti à l'impact, parce que parfois les nouvelles de la mémoire est allouée à la même adresse.

  • Double gratuit se produit surtout lorsqu'il y a deux pointeurs pointant vers la même adresse.

Logique des erreurs peuvent entraîner la corruption des données trop.

Éviter la réutilisation libéré des pointeurs

Bref: l'Accès libéré pointeurs peuvent entraîner la corruption des données si malloc() alloue de la mémoire dans le même endroit, à moins que le libéré pointeur est défini à l' NULL

  • Il n'ya aucune garantie que le programme se bloque lors de l'accès à l' NULL pointeur, si le décalage est assez grand (someStruct->lastMember, theArray[someBigNumber]). Au lieu de s'écraser, il y aura de la corruption de données.

  • Paramètre le pointeur à l' NULL ne peut pas résoudre le problème d'avoir un autre pointeur avec la même valeur du pointeur.

Les questions

Voici un post contre l'aveuglette paramètre un pointeur vers NULL après la libération.

  • Lequel est le plus difficile à déboguer?
  • Est-il possible de prendre les deux?
  • Quelle est la probabilité que de tels bugs entraîner la corruption des données au lieu de s'écraser?

Se sentir libre pour développer cette question.

26voto

steveha Points 24808

Le second est bien plus important: la ré-utilisation d'un libéré pointeur peut être une erreur subtile. Votre code conserve droit sur le travail, et puis se bloque sans raison parce que certains apparemment sans rapport avec le code écrit dans la mémoire que la ré-utilisé pointeur arrive à pointer à.

Une fois, j'ai eu à travailler sur un vraiment buggy programme de quelqu'un d'autre a écrit. Mon instinct me dit que de nombreux bugs ont été liés à la bâclée tente de garder à l'aide de pointeurs lors de la libération de la mémoire; j'ai modifié le code pour définir les pointeurs à NULL après la libération de la mémoire, et bam, le pointeur null exceptions ont commencé à venir. Après je fixe tout le pointeur null exceptions, soudain, le code est beaucoup plus stable.

Dans mon code, je n'ai appeler mon propre fonction qui est un wrapper autour de free(). Il prend un pointeur vers un pointeur, et les valeurs null le pointeur lors de la libération de la mémoire. Et avant qu'il appelle libre, il appelle Assert(p != NULL); de sorte qu'il prises encore les tentatives de double-libre le même pointeur.

Mon code fait d'autres choses aussi, comme (dans la version DEBUG uniquement) de remplissage de la mémoire avec une évidente valeur immédiatement après l'affectation, faire la même droite avant d'appeler free() dans le cas où il existe une copie du pointeur, etc. Les détails ici.

EDIT: par une demande, voici un exemple de code.

void
FreeAnything(void **pp)
{
    void *p;

    AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
    if (!pp)
        return;

    p = *pp;
    AssertWithMessage(p != NULL, "attempt to free a null pointer");
    if (!p)
        return;

    free(p);
    *pp = NULL;
}


// FOO is a typedef for a struct type
void
FreeInstanceOfFoo(FOO **pp)
{
    FOO *p;

    AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
    if (!pp)
        return;

    p = *pp;
    AssertWithMessage(p != NULL, "attempt to free a null FOO pointer");
    if (!p)
        return;

    AssertWithMessage(p->signature == FOO_SIG, "bad signature... is this really a FOO instance?");

    // free resources held by FOO instance
    if (p->storage_buffer)
        FreeAnything(&p->storage_buffer);
    if (p->other_resource)
        FreeAnything(&p->other_resource);

    // free FOO instance itself
    free(p);
    *pp = NULL;
}

Commentaires:

Vous pouvez le voir dans la deuxième fonction que j'ai besoin de vérifier les deux pointeurs de ressource pour voir si elles ne sont pas nulles, et ensuite appeler FreeAnything(). C'est à cause de l' assert() qui se plaignent d'un pointeur null. Je n'ai que faire valoir afin de détecter une tentative de double-libre, mais je ne pense pas qu'il a réellement pris beaucoup de bugs pour moi; si vous voulez quitter l'affirme, alors vous pouvez laisser la case juste et toujours appel FreeAnything(). D'autres que de l'affirmer, rien de mauvais ne se passe lorsque vous essayez de libérer un pointeur null avec FreeAnything() car il vérifie le pointeur et retourne juste si c'était déjà nulle.

Mes noms de fonction sont plutôt plus laconique, mais j'ai essayé de chercher l'auto-documentation des noms pour cet exemple. Aussi, dans mon code, j'ai de débogage uniquement code qui remplit les tampons avec la valeur 0xDC avant d'appeler free() , de sorte que si j'ai un supplément de pointeur à cette même mémoire (celui qui n'est pas entaché de nullité), c'est vraiment évident que les données qu'il pointe vers est faux de données. J'ai une macro, DEBUG_ONLY(), qui compile à rien sur un non-debug; et une macro FILL() qui ne un sizeof() sur une struct. Ces deux fonctionnent aussi bien: sizeof(FOO) ou sizeof(*pfoo). Voici donc l' FILL() macro:

#define FILL(p, b) \
    (memset((p), b, sizeof(*(p)))

Voici un exemple d'utilisation de FILL() mettre la 0xDC valeurs en avant d'appeler:

if (p->storage_buffer)
{
    DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);)
    FreeAnything(&p->storage_buffer);
}

Un exemple d'utilisation de cette:

PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2);
DoSomethingWithFooInstance(pfoo);
FreeInstanceOfFoo(&pfoo);
assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires

8voto

Steve Jessop Points 166970

Je ne fais pas cela. Je ne me souviens particulièrement des bugs qui aurait été plus facile à traiter si je l'ai fait. Mais cela dépend vraiment de la façon dont vous écrivez votre code. Il y a environ trois situations où je gratuit rien:

  • Lorsque le pointeur de la holding elle est sur le point d'aller hors de portée, ou d'une partie d'un objet qui est sur le point d'aller hors de portée ou d'être libéré.
  • Quand je suis en remplacement de l'objet par un nouveau (comme avec une redistribution, par exemple).
  • Lorsque je publie un objet qui est éventuellement présent.

Dans le troisième cas, vous définissez le pointeur à NULL. Ce n'est pas précisément parce que vous êtes le libérer, c'est parce que l'autre-il-est est facultative, bien sûr, la valeur NULL est une valeur particulière qui signifie "je n'ai pas obtenu un".

Dans les deux premiers cas, la définition du pointeur à NULL me semble être occupé avec le travail, avec aucun but particulier:

int doSomework() {
    char *working_space = malloc(400*1000);
    // lots of work
    free(working_space);
    working_space = NULL; // wtf? In case someone has a reference to my stack?
    return result;
}

int doSomework2() {
    char * const working_space = malloc(400*1000);
    // lots of work
    free(working_space);
    working_space = NULL; // doesn't even compile, bad luck
    return result;
}

void freeTree(node_type *node) {
    for (int i = 0; i < node->numchildren; ++i) {
        freeTree(node->children[i]);
        node->children[i] = NULL; // stop wasting my time with this rubbish
    }
    free(node->children);
    node->children = NULL; // who even still has a pointer to node?

    // Should we do node->numchildren = 0 too, to keep
    // our non-existent struct in a consistent state?
    // After all, numchildren could be big enough
    // to make NULL[numchildren-1] dereferencable,
    // in which case we won't get our vital crash.

    // But if we do set numchildren = 0, then we won't
    // catch people iterating over our children after we're freed,
    // because they won't ever dereference children.

    // Apparently we're doomed. Maybe we should just not use
    // objects after they're freed? Seems extreme!
    free(node);
}

int replace(type **thing, size_t size) {
    type *newthing = copyAndExpand(*thing, size);
    if (newthing == NULL) return -1;
    free(*thing);
    *thing = NULL; // seriously? Always NULL after freeing?
    *thing = newthing;
    return 0;
}

Il est vrai que NULLE-ment le pointeur peut plus évident si vous avez un bug où vous essayez de déréférencement d'elle après la libération. Un déréférencement n'a probablement pas mal si vous n'avez pas NULL le pointeur, mais il est mal dans le long terme.

Il est également vrai que NULLE-ment le pointeur obscurcit bogues lorsque vous double-gratuit. Le second ne fait pas de mal si vous n'NULL le pointeur, mais il est mal dans le long terme (car il trahit le fait que l'objet de votre cycle de vie sont cassés). Vous pouvez faire valoir les choses sont non-nulles quand vous libérer, mais que les résultats dans le code suivant pour gratuit une structure qui détient une valeur facultative:

if (thing->cached != NULL) {
    assert(thing->cached != NULL);
    free(thing->cached);
    thing->cached = NULL;
}
free(thing);

Ce que le code que vous avez dit, c'est que vous avez trop loin. Il devrait être:

free(thing->cached);
free(thing);

Je dis, NULL le pointeur si c'est censé rester utilisable. Si ce n'est plus utilisable, préférable de ne pas le faire à tort semble être, en les mettant dans un potentiellement significative de la valeur comme la valeur NULL. Si vous voulez provoquer une défaillance de page, utiliser une plate-forme dépendant de la valeur de ce qui n'est pas dereferancable, mais qui le reste de votre code ne sera pas traiter comme un "tout va bien dans le meilleur des mondes" de la valeur:

free(thing->cached);
thing->cached = (void*)(0xFEFEFEFE);

Si vous ne pouvez pas trouver une telle constante sur votre système, vous pouvez être en mesure d'allouer un non-lisibles et/ou de non-inscriptible page, et utiliser l'adresse de celui-ci.

3voto

Kosi2801 Points 9487

Si vous ne définissez pas le pointeur à NULL, il y a une petite chance que votre application continue de fonctionner dans un état indéfini et se bloque suite à un complètement différent point. Ensuite, vous allez passer beaucoup de temps avec le débogage d'un inexistante erreur avant de le savoir, que c'est une mémoire de la corruption à partir plus tôt.

J'avais définir le pointeur à NULL, parce que les chances sont plus que vous serez frappé l'endroit de l'erreur plus tôt que si vous n'avez pas défini à NULL. L'erreur de logique, de libérer de la mémoire un deuxième temps est encore à être pensé et l'erreur que votre application ne bloque PAS sur de pointeur null accès avec un assez grand décalage est à mon avis complètement académique mais pas impossible.

Conclusion: j'irais pour paramètre le pointeur à NULL.

3voto

Carsten Kuckuk Points 693

La réponse dépend de (1) la taille du projet, (2) durée de vie prévue de votre code, et (3) la taille de l'équipe. Sur un petit projet avec une courte durée de vie, vous pouvez ignorer le réglage des pointeurs à NULL, et juste le long de débogage.

Sur une grande, longue durée de vie du projet, il y a de bonnes raisons de créer des pointeurs à NULL: (1) une programmation Défensive est toujours bon. Votre code peut être ok, mais le débutant à côté de la porte pourrait encore du mal avec les pointeurs (2) Ma conviction personnelle est que toutes les variables doivent contenir que des valeurs valides en tout temps. Après une suppression / free, le pointeur n'est pas une valeur valide de plus, il doit donc être retiré de cette variable. En la remplaçant par la valeur NULL (la seule valeur du pointeur est toujours valide) est une bonne étape. (3) le Code ne meurt jamais. Elle obtient toujours réutilisés, et souvent d'une manière que vous n'avez pas imaginé à l'époque vous l'avez écrit. Votre segment de code peut être compilé en C++ contexte, et probablement obtenir déplacé à un destructeur ou une méthode qui est appelée par un destructeur. Les interactions des méthodes virtuelles et les objets qui sont en train d'être détruits sont subtiles, des pièges, même pour de très programmeurs expérimentés. (4) Si votre code finit par être utilisé dans un environnement multi-thread contexte, un autre thread peut lire cette variable et essayez d'y accéder. De tels contextes surviennent souvent lorsque le code de legs est enveloppé et réutilisés dans un serveur web. Donc, la meilleure façon de libérer de la mémoire (à partir d'un paranoïaque point de vue) est de (1) copie le pointeur sur une variable locale, (2) définir la variable d'origine NULL, (3) supprimer/gratuit la variable locale.

1voto

Sebastian Points 3419

En C++ pourrait attraper à la fois par la mise en œuvre de votre propre pointeur intelligent (ou dérivant de la implémentations existantes) et de mettre en œuvre quelque chose comme:

void release() {
    assert(m_pt!=NULL);
    T* pt = m_pt;
    m_pt = NULL;
    free(pt);
}

T* operator->() {
    assert(m_pt!=NULL);
    return m_pt;
}

Sinon, en C, vous pourriez au moins donner deux macros pour le même effet:

#define SAFE_FREE(pt) \
    assert(pt!=NULL); \
    free(pt); \
    pt = NULL;

#define SAFE_PTR(pt) assert(pt!=NULL); pt

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X