Question Qu'est-ce qui se passe VRAIMENT quand vous ne libérez pas après malloc?


Cela me dérange depuis longtemps.

Nous sommes tous enseignés à l'école (du moins, je l'étais) que vous devez libérer chaque pointeur qui est alloué. Je suis un peu curieux, cependant, sur le coût réel de ne pas libérer de la mémoire. Dans certains cas évidents, comme quand malloc est appelé dans une boucle ou une partie d'une exécution de thread, il est très important de libérer afin qu'il n'y ait pas de fuites de mémoire. Mais considérons les deux exemples suivants:

D'abord, si j'ai du code, c'est quelque chose comme ça:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Quel est le vrai résultat ici? Je pense que le processus meurt et que l’espace de stockage est de toute façon perdu, il n’ya donc pas de mal à manquer l’appel à free (cependant, je reconnais l'importance de l'avoir de toute façon pour la fermeture, la maintenabilité et les bonnes pratiques). Ai-je raison dans cette réflexion?

Deuxièmement, disons que j'ai un programme qui agit un peu comme un shell. Les utilisateurs peuvent déclarer des variables comme aaa = 123 et ceux-ci sont stockés dans une structure de données dynamique pour une utilisation ultérieure. Clairement, il semble évident que vous utilisiez une solution qui appelle une fonction * alloc (hashmap, liste liée, quelque chose comme ça). Pour ce type de programme, cela n'a aucun sens de toujours être libre après avoir appelé malloc car ces variables doivent être présentes à tout moment pendant l'exécution du programme et il n'y a aucun moyen (que je peux voir) d'implémenter cela avec un espace alloué statiquement. Est-ce mauvais design d'avoir un tas de mémoire qui est alloué mais seulement libéré dans le cadre de la fin du processus? Si oui, quelle est l'alternative?


431
2018-03-17 15:29


origine


Réponses:


À peu près tous les systèmes d'exploitation modernes vont récupérer tout l'espace mémoire alloué après la sortie d'un programme. La seule exception à laquelle je puisse penser pourrait être quelque chose comme Palm OS, où le stockage statique du programme et la mémoire d'exécution sont à peu près les mêmes, de sorte que le fait de ne pas les libérer pourrait nécessiter davantage de stockage. (Je ne fais que spéculer ici.)

Donc, généralement, il n'y a aucun mal à cela, sauf le coût d'exécution d'avoir plus de stockage que vous avez besoin. Certainement, dans l'exemple que vous donnez, vous voulez conserver la mémoire pour une variable qui pourrait être utilisée jusqu'à ce qu'elle soit effacée.

Cependant, il est préférable de libérer de la mémoire dès que vous n'en avez plus besoin et de libérer tout ce que vous avez encore à la sortie du programme. Il s'agit plus d'un exercice consistant à savoir quel souvenir vous utilisez et à vous demander si vous en avez encore besoin. Si vous ne suivez pas, vous pourriez avoir des fuites de mémoire.

D'un autre côté, le même avertissement pour fermer vos fichiers à la sortie a un résultat beaucoup plus concret - si vous ne le faites pas, les données que vous leur écrivez peuvent ne pas être vidées, ou s'ils sont un fichier temporaire, ils peuvent ne pas être supprimé lorsque vous avez terminé. En outre, les transactions de base de données doivent avoir leurs transactions validées, puis fermées lorsque vous en avez terminé avec elles. De même, si vous utilisez un langage orienté objet tel que C ++ ou Objective C, ne pas libérer un objet lorsque vous en aurez terminé signifie que le destructeur ne sera jamais appelé et que toutes les ressources responsables de la classe risquent de ne pas être nettoyées.


303
2018-03-17 15:32



Oui vous avez raison, votre exemple ne fait pas de mal (du moins pas sur la plupart des systèmes d'exploitation modernes). Toute la mémoire allouée par votre processus sera récupérée par le système d'exploitation une fois le processus terminé.

La source: Allocation et Mythes GC (Alerte PostScript!)

Allocation Mythe 4: programmes non récupérés   devrait toujours libérer toute la mémoire   ils allouent.

La vérité: Omis   deallocations fréquemment exécutés   code cause des fuites de plus en plus. Elles sont   rarement acceptable. mais les programmes qui   conserver la mémoire la plus allouée jusqu'à ce que   la sortie du programme fonctionne souvent mieux   sans aucune intervention.   Malloc est beaucoup plus facile à mettre en œuvre si   il n'y a pas de gratuit.

Dans la plupart des cas, désallouer de la mémoire   juste avant que la sortie du programme soit inutile.   Le système d'exploitation le récupérera quand même. Gratuit   va toucher et page dans les morts   objets; l'OS ne le fera pas.

Conséquence: Soyez prudent avec "fuite   détecteurs "qui comptent les allocations.   Certaines "fuites" sont bonnes!

Cela dit, vous devriez vraiment essayer d'éviter toute fuite de mémoire!

Deuxième question: votre conception est correcte. Si vous avez besoin de stocker quelque chose jusqu'à ce que votre application se termine, vous pouvez le faire avec l'allocation de mémoire dynamique. Si vous ne connaissez pas la taille requise à l'avance, vous ne pouvez pas utiliser de mémoire allouée statiquement.


92
2018-03-17 15:32



=== Qu'en est-il de vérification future et réutilisation du code? ===

Si vous ne pas écrivez le code pour libérer les objets, alors vous limitez le code à la sécurité d'utilisation seulement quand vous pouvez compter sur la libération de la mémoire par le processus en cours de fermeture ... c'est-à-dire de petits projets à usage unique ou un moyen"[1] projets) ... où vous savez quand le processus prendra fin.

Si vous faire écrivez le code qui libère () tout votre mémoire allouée dynamiquement, ensuite vous testez le code et laissez les autres l'utiliser dans un projet plus grand.


[1] concernant les projets "jetables". Le code utilisé dans les projets «jetables» a un moyen de ne pas être jeté. La prochaine chose que vous savez que dix ans se sont écoulés et votre code "jetable" est toujours utilisé).

J'ai entendu une histoire au sujet d'un gars qui a écrit du code juste pour s'amuser afin de rendre son matériel plus efficace. Il a dit "juste un passe-temps, ne sera pas grand et professionnelDes années plus tard, beaucoup de gens utilisent son code "passe-temps".


49
2018-03-17 16:43



Vous avez raison, aucun mal n'est fait et il est plus rapide de simplement quitter

Il y a plusieurs raisons à cela:

  • Tous les environnements de bureau et serveur libèrent simplement tout l'espace mémoire sur exit (). Ils ne sont pas au courant des structures de données internes au programme telles que les tas.

  • Presque toutes free() implémentations ne fais jamais renvoyer la mémoire au système d'exploitation de toute façon.

  • Plus important encore, c'est une perte de temps lorsqu'elle est effectuée juste avant la sortie (). À la sortie, les pages de mémoire et l’espace d’échange sont simplement libérés. En revanche, une série d'appels gratuits () brûlent le temps processeur et peuvent entraîner des opérations de pagination de disque, des échecs de cache et des expulsions de cache.

En ce qui concerne la possibilité de la réutilisation future du code justifiant la certitude des opérations inutiles: c'est une considération, mais ce n'est sans doute pas le Agile façon. YAGNI!


44
2017-12-28 06:04



Je suis complètement en désaccord avec tous ceux qui disent que le PO est correct ou qu'il n'y a pas de mal.

Tout le monde parle d'un OS moderne et / ou hérité.

Mais que faire si je suis dans un environnement où je n'ai tout simplement pas de système d'exploitation? Où il n'y a rien?

Imaginez maintenant que vous utilisez des interruptions filaires et allouez de la mémoire. Dans la norme C ISO / CEI: 9899 est la durée de vie de la mémoire indiquée comme:

7.20.3 Fonctions de gestion de la mémoire

1 L'ordre et la contiguïté de stockage alloués par appels successifs au calloc,   Les fonctions malloc et realloc ne sont pas spécifiées. Le pointeur est retourné si l'allocation   réussit est convenablement aligné de sorte qu'il peut être affecté à un pointeur vers n'importe quel type d'objet   puis utilisé pour accéder à un tel objet ou un tableau de tels objets dans l'espace alloué   (jusqu'à ce que l'espace soit explicitement désalloué). La durée de vie d'un objet alloué s'étend   de l'attribution jusqu'à la désallocation. [...]

Donc, il ne doit pas être donné que l'environnement fait le travail de libération pour vous. Sinon, il serait ajouté à la dernière phrase: "Ou jusqu'à ce que le programme se termine."

Donc en d'autres termes: Ne pas libérer de la mémoire n'est pas seulement une mauvaise pratique. Il produit un code non portable et non conforme à C. Ce qui peut au moins être considéré comme "correct, si ce qui suit: [...], est supporté par l'environnement".

Mais dans les cas où vous n'avez aucun système d'exploitation, personne ne fait le travail pour vous (Je sais généralement que vous n'allouez pas et ne réallouez pas la mémoire sur les systèmes embarqués,  mais il y a des cas où vous voudrez peut-être.)

Donc, en général, en clair C (comme l’OP est balisé),  cela produit simplement du code erroné et non portable.


20
2018-05-08 07:42



Je libère généralement chaque bloc alloué une fois que je suis sûr que j'en ai fini avec lui. Aujourd'hui, le point d'entrée de mon programme pourrait être main(int argc, char *argv[]) , mais demain ça pourrait être foo_entry_point(char **args, struct foo *f) et tapé comme un pointeur de fonction.

Donc, si cela arrive, j'ai maintenant une fuite.

En ce qui concerne votre deuxième question, si mon programme prenait comme a = 5, j'allouerais de l'espace pour un, ou ré-allouerais le même espace sur un a = "foo" ultérieur. Cela resterait attribué jusqu'à ce que:

  1. L'utilisateur a tapé 'unset a'
  2. Ma fonction de nettoyage a été entrée, soit la gestion d'un signal ou l'utilisateur a tapé «quitter»

Je ne peux penser à aucun moderne Système d'exploitation qui ne récupère pas la mémoire après un processus. Ensuite, free () est bon marché, pourquoi ne pas nettoyer? Comme d'autres l'ont dit, les outils comme valgrind sont parfaits pour repérer les fuites dont vous avez vraiment besoin de s'inquiéter. Même si les blocs de votre exemple seraient étiquetés comme «encore joignables», c'est juste un bruit supplémentaire dans la sortie lorsque vous essayez de vous assurer qu'il n'y a pas de fuites.

Un autre mythe est "Si sa in main (), je ne dois pas le libérer", ceci est incorrect. Considérez ce qui suit:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Si cela venait avant le forking / daemonizing (et en théorie en cours d'exécution pour toujours), votre programme vient de divulguer une taille indéterminée de 255 fois.

Un bon programme bien écrit devrait toujours être nettoyé après lui-même. Libérer toute la mémoire, vider tous les fichiers, fermer tous les descripteurs, dissocier tous les fichiers temporaires, etc. Cette fonction de nettoyage doit être atteinte à la fin normale ou à la réception de divers types de signaux fatals, sauf si vous souhaitez laisser des fichiers détecter un crash et reprendre.

Vraiment, soyez gentil avec l'âme pauvre qui doit maintenir vos affaires quand vous passez à d'autres choses ... donnez-leur 'valgrind clean' :)


19
2018-03-18 08:36



Il est tout à fait correct de laisser la mémoire sans valeur lorsque vous sortez; malloc () alloue la mémoire de la zone mémoire appelée "heap", et le tas complet d'un processus est libéré à la sortie du processus.

Cela étant dit, une raison pour laquelle les gens insistent sur le fait qu'il est bon de tout libérer avant de sortir est que les débogueurs de mémoire (par exemple, valgrind sous Linux) détectent les blocs non-fictifs comme des fuites de mémoire, et si vous avez de vraies fuites de mémoire. plus difficile de les repérer si vous obtenez également des résultats "faux" à la fin.


11
2018-03-17 15:33



Si vous utilisez la mémoire que vous avez allouée, alors vous ne faites rien de mal. Cela devient un problème lorsque vous écrivez des fonctions (autres que main) qui allouent de la mémoire sans la libérer, et sans la rendre disponible pour le reste de votre programme. Ensuite, votre programme continue à fonctionner avec la mémoire qui lui est allouée, mais aucun moyen de l'utiliser. Votre programme et d'autres programmes en cours sont privés de cette mémoire.

Edit: Il n'est pas exact à 100% de dire que d'autres programmes en cours sont privés de cette mémoire. Le système d’exploitation peut toujours leur permettre de l’utiliser au détriment de la conversion de votre programme en mémoire virtuelle (</handwaving>). Le fait est, cependant, que si votre programme libère de la mémoire qu'il n'utilise pas alors un échange de mémoire virtuelle est moins susceptible d'être nécessaire.


11
2018-03-17 15:34



Ce code fonctionnera généralement bien, mais considérons le problème de la réutilisation du code.

Vous avez peut-être écrit un extrait de code qui ne libère pas la mémoire allouée, il est exécuté de telle sorte que la mémoire est ensuite récupérée automatiquement. Ça semble aller.

Ensuite, quelqu'un d'autre copie votre extrait de code dans son projet de telle sorte qu'il soit exécuté mille fois par seconde. Cette personne a maintenant une énorme fuite de mémoire dans son programme. Pas très bon en général, généralement fatal pour une application serveur.

La réutilisation du code est typique dans les entreprises. Généralement, l'entreprise possède tout le code produit par ses employés et chaque service peut réutiliser tout ce que l'entreprise possède. Donc, en écrivant un tel code «d'apparence innocente», vous causez des maux de tête potentiels à d'autres personnes. Cela peut vous faire virer.


11
2018-03-18 08:00



Quel est le vrai résultat ici?

Votre programme a fui la mémoire. Selon votre système d'exploitation, il mai ont été récupérés.

Plus moderne bureau systèmes d'exploitation faire récupérer la mémoire qui a fui à la fin du processus, ce qui rend tristement courant d'ignorer le problème, comme le montrent de nombreuses autres réponses ici.)

Mais vous comptez sur une fonctionnalité de sécurité que vous ne devriez pas compter, et votre programme (ou fonction) pourrait fonctionner sur un système où ce comportement Est-ce que entraîner une fuite de mémoire «dure», prochain temps.

Vous utilisez peut-être en mode noyau ou sur des systèmes d'exploitation vintage / embarqués qui n'utilisent pas la protection de la mémoire comme compromis. (Les MMU occupent de l'espace, la protection de la mémoire coûte des cycles de processeur supplémentaires, et ce n'est pas trop demander à un programmeur de nettoyer après lui).

Vous pouvez utiliser et réutiliser la mémoire comme bon vous semble, mais assurez-vous de libérer toutes les ressources avant de quitter.


5
2018-01-13 09:46