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