void destroyFoo(Foo** foo)
{
if (!(*foo)) return;
Foo *tmpFoo = *foo;
*foo = NULL;
memset(tmpFoo, 0, sizeof(Foo));
free(tmpFoo);
}
Le code de votre collègue est mauvais parce que
- il se plantera si
foo
est NULL
- il est inutile de créer des variables supplémentaires
- il est inutile de mettre les valeurs à zéro
- libérer une structure directement ne fonctionne pas si elle contient des choses qui doivent être libérées
Je pense que votre collègue a peut-être à l'esprit le cas d'utilisation suivant
Foo* a = NULL;
Foo* b = createFoo();
destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);
Dans ce cas, il faut procéder comme suit. essayez ici
void destroyFoo(Foo** foo)
{
if (!foo || !(*foo)) return;
free(*foo);
*foo = NULL;
}
Tout d'abord, nous devons jeter un coup d'œil sur Foo
Supposons qu'il ressemble à ceci
struct Foo
{
// variables
int number;
char character;
// array of float
int arrSize;
float* arr;
// pointer to another instance
Foo* myTwin;
};
Pour définir comment il doit être détruit, définissons d'abord comment il doit être créé
Foo* createFoo (int arrSize, Foo* twin)
{
Foo* t = (Foo*) malloc(sizeof(Foo));
// initialize with default values
t->number = 1;
t->character = '1';
// initialize the array
t->arrSize = (arrSize>0?arrSize:10);
t->arr = (float*) malloc(sizeof(float) * t->arrSize);
// a Foo is a twin with only one other Foo
t->myTwin = twin;
if(twin) twin->myTwin = t;
return t;
}
Nous pouvons maintenant écrire une fonction de destruction opposée à la fonction de création
Foo* destroyFoo (Foo* foo)
{
if (foo)
{
// we allocated the array, so we have to free it
free(t->arr);
// to avoid broken pointer, we need to nullify the twin pointer
if(t->myTwin) t->myTwin->myTwin = NULL;
}
free(foo);
return NULL;
}
Test essayez ici
int main ()
{
Foo* a = createFoo (2, NULL);
Foo* b = createFoo (4, a);
a = destroyFoo(a);
b = destroyFoo(b);
printf("success");
return 0;
}
2 votes
Tout dépend des cas d'utilisation et de ce que vous essayez de réaliser. Notez également que vous pouvez appeler
free
avec un pointeur nul, il ne fait rien.3 votes
Ce n'est pas le bon type de question pour stackoverflow : les réponses ne sont pas claires. Il y a des avantages à faire les choses dans le deuxième bloc de code, mais il y a aussi des inconvénients. La question de savoir si les avantages l'emportent sur les coûts n'est qu'une question d'opinion.
0 votes
La première hypothèse est erronée - il faut que ce soit
if ( foo ) free( foo)
. Sinon, avecif (!foo)
la mémoire ne sera jamais libérée.0 votes
@i486 Merci pour l'avis, je l'ai modifié.
1 votes
La seconde méthode est meilleure et plus sûre. Vous pouvez également ajouter
assert( foo )
au début deDestroyFoo
afin de détecter d'éventuels appels erronés dans la version de débogage.0 votes
@PaulHankin Pourriez-vous nous donner plus de détails sur les inconvénients du deuxième bloc, à l'exception du fait qu'il est plus lent ?
0 votes
@i486 assert(foo) est un excellent conseil, merci !
0 votes
@FeritBuyukkececi c'est plus de code, c'est plus complexe, c'est plus lent. De plus, il faut que le pointeur soit adressable (même s'il l'est généralement).
1 votes
Cela n'empêchera pas les problèmes de concurrence. Le compilateur est fondamentalement libre d'optimiser le code sans les contraintes de la machine abstraite ("comportement observable"). L'exemple est donc tout simplement absurde et témoigne d'une incompréhension fondamentale. Le langage C ne prend pas en charge l'exécution simultanée à ce niveau, et ce pour de bonnes raisons.
0 votes
@JoachimPileborg : Il y a quelques cas (bien que rares !) où vous voudriez une condition avant de faire une demande d'autorisation.
free()
Valgrind, par exemple, se plaint de tels cas. Bien sûr, dans certains cas, vous pouvez VOULOIR Valgrind se plaindra (cela dépend principalement de la façon dont votre code vérifie les pointeurs NULL ailleurs avant de les libérer).0 votes
@Olaf : Je suis presque sûr que le
memset()
pourrait être optimisé de toute façon, si le compilateur décide quetmpFoo
est ensuite inutilisé à cause de lafree()
et si les deux sont traités comme des modules intégrés. Après tout, en ce qui concerne la machine abstraite, l'effet de l'absence dememset()
est le même. Cela supprime donc que (potentiel) aussi.0 votes
@TimCas : Peu probable. Peut-être avec LTO ou si toutes les fonctions/variables concernées ont un lien interne. Mais cela nécessiterait une analyse statique complète du code. Cependant,
memset
ne faisait même pas partie de mon commentaire, le vôtre n'a rien à voir. J'ai parlé de problèmes de concurrence.0 votes
@Olaf : Je dis simplement que le
memset()
est également inutile. Et tout l'intérêt de lamemset()
est pour la sécurité (par exemple, effacer la clé privée de la RAM) --- du moins je le suppose. Et voudriez-vous vous fier à un comportement très spécifique du compilateur (qui peut changer d'une version à l'autre) pour la sécurité ?1 votes
@TimCas : Je suis d'accord avec le
memset
est inutile. En cas d'accès ultérieur erroné à l'ancien objet, il se peut que la zone ait déjà été allouée à un autre objet et qu'elle ne soit donc plus mise à zéro. Les clés privées sont de toute façon spéciales etmemset
est inutile ici aussi, parce qu'il peut déjà avoir été lu avant la fin de l'année.free
de l'ingestion.1 votes
Comme vous pouvez l'appeler
free
avec un pointeur nul, la première fonction est inutile. Appelez directement free. (ou utiliser la deuxième méthode)9 votes
Ne mettez pas le bloc libéré à 0x00. Définissez-le à des copies répétées de 0xDEADBEEF. Si vous déboguez un crash et que vous constatez que vous êtes au milieu d'un bloc de bœuf mort, vous savez que vous êtes à l'intérieur de la mémoire libérée.