Que se passe-t-il VRAIMENT lorsque vous ne libérez pas après malloc?

538

C'est quelque chose qui me dérange depuis des lustres maintenant.

On nous enseigne tous à l'école (du moins, je l'étais) que vous DEVEZ libérer chaque pointeur qui est alloué. Je suis un peu curieux, cependant, au sujet du coût réel de ne pas libérer de mémoire. Dans certains cas évidents, comme quand mallocest appelé à l'intérieur d'une boucle ou d'une partie d'une exécution de thread, il est très important de le libérer afin qu'il n'y ait pas de fuite de mémoire. Mais considérons les deux exemples suivants:

Tout d'abord, si j'ai du code, c'est quelque chose comme ceci:

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 mémoire a disparu de toute façon, donc il n'y a aucun mal à manquer l'appel à free(cependant, je reconnais l'importance de l'avoir quand même pour la fermeture, la maintenabilité et les bonnes pratiques). Ai-je raison dans cette pensée?

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

Scott
la source
7
@NTDLS La magie du système de notation fonctionne réellement pour une fois: 6 ans plus tard et la réponse "la plus méritante" a en effet atteint le sommet.
zxq9
15
Les gens ci-dessous continuent de dire qu'un bon système d'exploitation moderne nettoie mais que faire si le code s'exécute en mode noyau (par exemple, pour des raisons de performances)? Les programmes en mode noyau (sous Linux par exemple) sont-ils mis en sandbox? Sinon, je pense que vous devrez tout libérer manuellement, je suppose, avant même toute terminaison anormale comme avec abort ().
Dr. Person Person II
3
@ Dr.PersonPersonII Oui, le code exécuté en mode noyau doit généralement tout libérer manuellement.
zwol
1
Je voudrais ajouter que free(a)cela ne fait vraiment rien pour libérer de la mémoire! Il réinitialise simplement certains pointeurs dans l'implémentation libc de malloc qui gardent une trace des morceaux de mémoire disponibles dans une grande page de mémoire mappée (communément appelée le "tas"). Cette page ne sera toujours libérée qu'à la fin de votre programme, pas avant.
Marco Bonelli
1
Free () peut ou non libérer la mémoire. Il peut simplement marquer le bloc comme libéré, pour être récupéré ultérieurement, ou le lier dans une liste gratuite. Il peut le fusionner en blocs libres adjacents, ou il peut laisser cela pour une allocation ultérieure à faire. C'est tout un détail d'implémentation.
Jordan Brown

Réponses:

378

Presque tous les systèmes d'exploitation modernes récupèrent tout l'espace mémoire alloué après la fermeture d'un programme. La seule exception à laquelle je peux 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 la même chose, donc le fait de ne pas libérer pourrait entraîner une augmentation du stockage du programme. (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 n'en avez besoin. Certes, dans l'exemple que vous donnez, vous souhaitez conserver la mémoire d'une variable qui pourrait être utilisée jusqu'à ce qu'elle soit effacée.

Cependant, il est considéré comme un bon style 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 pour savoir quelle mémoire vous utilisez et pour savoir si vous en avez toujours besoin. Si vous ne suivez pas, vous pourriez avoir des fuites de mémoire.

D'un autre côté, le même avertissement de fermer vos fichiers à la sortie a un résultat beaucoup plus concret - si vous ne le faites pas, les données que vous leur avez écrites risquent de ne pas être vidées, ou s'il s'agit d'un fichier temporaire, elles pourraient ne pas être supprimé lorsque vous avez terminé. En outre, les descripteurs de base de données doivent avoir leurs transactions validées, puis fermées lorsque vous en avez terminé. De même, si vous utilisez un langage orienté objet comme 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 les ressources dont la classe est responsable pourraient ne pas être nettoyées.

Paul Tomblin
la source
16
Il serait probablement bon de mentionner que tout le monde n'utilise pas un système d'exploitation moderne, si quelqu'un prend votre programme (et qu'il fonctionne toujours sur un système d'exploitation qui ne récupère pas la mémoire) l'exécute alors GG.
user105033
79
Je considère vraiment que cette réponse est fausse: il faut toujours désallouer les ressources une fois que l'on en a fini avec, que ce soit les descripteurs de fichiers / mémoire / mutex. En ayant cette habitude, on ne fera pas ce genre d'erreur lors de la construction des serveurs. Certains serveurs devraient fonctionner 24h / 24 et 7j / 7. Dans ces cas, toute fuite de quelque sorte signifie que votre serveur finira par manquer de cette ressource et se bloquer / planter d'une manière ou d'une autre. Un programme utilitaire court, une fuite n'est pas si mal. Tout serveur, toute fuite, c'est la mort. Fais toi plaisir. Nettoyez-vous après vous-même. C'est une bonne habitude.
EvilTeach
120
Quelle partie de "Cependant, il est considéré comme un bon style 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." considérez-vous mal alors?
Paul Tomblin
24
Si vous avez une mémoire dont vous avez besoin jusqu'au moment où le programme se termine, et que vous n'exécutez pas sur un système d'exploitation primitif, libérer la mémoire juste avant de quitter est un choix stylistique, pas un défaut.
Paul Tomblin
30
@Paul - juste d'accord avec EvilTeach, n'est pas considéré comme un bon style pour libérer de la mémoire, il est incorrect de ne pas libérer de la mémoire. Votre libellé rend cela aussi important que de porter un mouchoir qui correspond à votre cravate. En fait, c'est au niveau du port du pantalon.
Heath Hunnicutt
110

Oui, vous avez raison, votre exemple ne fait aucun 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é.

Source: allocation et mythes GC (alerte PostScript!)

Mythe d'allocation 4: les programmes non récupérés doivent toujours désallouer toute la mémoire qu'ils allouent.

La vérité: les désallocations omises dans le code fréquemment exécuté provoquent des fuites croissantes. Ils sont rarement acceptables. mais les programmes qui conservent la mémoire la plus allouée jusqu'à la sortie du programme fonctionnent souvent mieux sans désallocation intermédiaire. Malloc est beaucoup plus facile à mettre en œuvre s'il n'y en a pas gratuitement.

Dans la plupart des cas, désallouer la mémoire juste avant la sortie du programme est inutile. L'OS le récupérera quand même. Le libre arbitre touchera et paginera dans les objets morts; l'OS ne le fera pas.

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

Cela dit, vous devriez vraiment essayer d'éviter toutes les fuites de mémoire!

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

compie
la source
3
Peut-être parce que la question, telle que je l'ai lue, est ce qui se passe réellement dans la mémoire qui a fui, et non pas si cet exemple spécifique est correct. Je ne voterais pas contre, car c'est toujours une bonne réponse.
DevinB
3
Il y avait probablement (au début Windows, au début Mac OS), et peut-être encore, des systèmes d'exploitation qui nécessitent des processus pour libérer de la mémoire avant de quitter, sinon l'espace n'est pas récupéré.
Pete Kirkham
C'est tout à fait correct, sauf si vous vous souciez de la fragmentation de la mémoire ou de manquer de mémoire - vous faites trop cela et les performances de vos applications disparaîtront. Outre les faits concrets, suivez toujours les meilleures pratiques et le développement de bonnes habitudes.
NTDLS
1
J'ai une réponse acceptée qui se situe actuellement autour de -11, donc il n'est même pas en lice pour le record.
Paul Tomblin
8
Je pense qu'il est faux d'expliquer la nécessité de libérer () la mémoire en disant "à cause du détecteur de fuite". Cela revient à dire "vous devez conduire lentement dans une rue de jeux car des policiers pourraient vous attendre avec un radar".
Sebastian Mach
57

=== Qu'en est-il de l' épreuvage futur et de la réutilisation du code ? ===

Si vous n'écrivez pas le code pour libérer les objets, vous limitez le code à une utilisation sûre uniquement lorsque vous pouvez dépendre de la mémoire libérée par la fermeture du processus ... c'est-à-dire une petite utilisation unique projets ou projets "jetables" [1] ) ... où vous savez quand le processus se terminera.

Si vous faites écrire le code libre () est tout mémoire allouée dynamiquement, alors vous êtes futur épreuvage le code et de laisser d' autres l' utiliser dans un projet plus vaste.


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

J'ai entendu une histoire à propos d'un gars qui a écrit du code juste pour le plaisir afin d'améliorer le fonctionnement de son matériel. Il a dit " juste un passe-temps, ne sera pas grand et professionnel ". Des années plus tard, beaucoup de gens utilisent son code "hobby".

Trevor Boyd Smith
la source
8
A voté pour les "petits projets". Il existe de nombreux grands projets qui ne libèrent pas très intentionnellement de la mémoire à la sortie car c'est une perte de temps si vous connaissez vos plateformes cibles. OMI, un exemple plus précis aurait été les «projets isolés». Par exemple, si vous créez une bibliothèque réutilisable qui sera incluse dans d'autres applications, il n'y a pas de point de sortie bien défini, vous ne devriez donc pas fuir de mémoire. Pour une application autonome, vous saurez toujours exactement quand le processus se termine et pouvez prendre une décision consciente de décharger le nettoyage sur le système d'exploitation (qui doit faire les vérifications dans les deux cas).
Dan Bechard
L'application d'hier est la fonction de bibliothèque d'aujourd'hui, et demain elle sera liée à un serveur de longue durée qui l'appelle des milliers de fois.
Adrian McCarthy
1
@AdrianMcCarthy: Si une fonction vérifie si un pointeur statique est nul, l'initialise avec malloc()s'il l'est et se termine si le pointeur est toujours nul, une telle fonction peut être utilisée en toute sécurité un nombre arbitraire de fois même si elle freen'est jamais appelée. Je pense qu'il vaut probablement la peine de distinguer les fuites de mémoire qui peuvent utiliser une quantité de stockage illimitée, par rapport aux situations qui ne peuvent que gaspiller une quantité de stockage finie et prévisible.
supercat
@supercat: Mon commentaire parlait de l'évolution du code au fil du temps. Bien sûr, la fuite d'une quantité limitée de mémoire n'est pas un problème. Mais un jour, quelqu'un voudra changer cette fonction pour qu'elle n'utilise plus de pointeur statique. Si le code n'a aucune disposition pour pouvoir libérer la mémoire pointée, cela va être un changement dur (ou pire, le changement sera mauvais et vous vous retrouverez avec une fuite illimitée).
Adrian McCarthy
1
@AdrianMcCarthy: Changer le code pour ne plus utiliser de pointeur statique nécessiterait probablement de déplacer le pointeur dans une sorte d'objet "contextuel" et d'ajouter du code pour créer et détruire de tels objets. Si le pointeur est toujours nulls'il n'y a pas d'allocation, et non nul lorsqu'une allocation existe, avoir du code libre l'allocation et définir le pointeur sur nullquand un contexte est détruit serait simple, en particulier par rapport à tout ce qui devrait être fait pour déplacer des objets statiques dans une structure de contexte.
supercat
52

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

Il y a plusieurs raisons à cela:

  • Tous les environnements de bureau et de serveur libèrent simplement tout l'espace mémoire à la sortie (). Ils ne connaissent pas les structures de données internes au programme telles que les tas.

  • Presque toutes les free()implémentations ne renvoient jamais de mémoire au système d'exploitation.

  • Plus important encore, c'est une perte de temps lorsque cela est fait juste avant la sortie (). À la sortie, les pages mémoire et l'espace d'échange sont simplement libérés. En revanche, une série d'appels free () brûle le temps processeur et peut entraîner des opérations de pagination sur disque, des échecs de cache et des expulsions de cache.

En ce qui concerne la possibilité de réutilisation future du code justifiant la certitude d'opérations inutiles: c'est une considération mais ce n'est sans doute pas la manière Agile . YAGNI!

DigitalRoss
la source
2
Une fois, j'ai travaillé sur un projet où nous avons passé un peu de temps à essayer de comprendre l'utilisation de la mémoire d'un programme (nous devions le prendre en charge, nous ne l'avons pas écrit). Sur la base de l'expérience, je suis anecdotique avec votre deuxième puce. Cependant, j'aimerais vous entendre (ou quelqu'un) fournir davantage de preuves que cela est vrai.
user106740
3
Tant pis, j'ai trouvé la réponse: stackoverflow.com/questions/1421491/… . Merci!
user106740
@aviggiano qui s'appelle YAGNI.
v.oddou
Le principe YAGNI fonctionne dans les deux sens: vous n'aurez jamais besoin d'optimiser le chemin d'arrêt. Optimisations prématurées et tout ça.
Adrian McCarthy
26

Je suis complètement en désaccord avec tous ceux qui disent que OP est correct ou qu'il n'y a aucun 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 de style thread et allouez de la mémoire. Dans la norme C ISO / IEC: 9899, ​​la durée de vie de la mémoire est indiquée comme suit:

7.20.3 Fonctions de gestion de la mémoire

1 L'ordre et la contiguïté du stockage alloués par les appels successifs aux fonctions calloc, malloc et realloc ne sont pas spécifiés. Le pointeur renvoyé si l'allocation réussit est correctement aligné de sorte qu'il peut être affecté à un pointeur sur n'importe quel type d'objet, puis utilisé pour accéder à un tel objet ou à un tableau de ces 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'allocation jusqu'à la désallocation. [...]

Il ne faut donc pas dire que l'environnement fait le travail de libération pour vous. Sinon, il serait ajouté à la dernière phrase: "Ou jusqu'à la fin du programme."

En d'autres termes: ne pas libérer de mémoire n'est pas seulement une mauvaise pratique. Il produit un code non portable et non C conforme. Ce qui peut au moins être considéré comme «correct, si ce qui suit: [...], est pris en charge 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 que généralement vous n'allouez pas et ne réallouez pas de mémoire sur les systèmes embarqués, mais il y a des cas où vous pouvez le vouloir.)

Donc, en termes généraux de C (comme lequel l'OP est étiqueté), cela produit simplement du code erroné et non portable.

dhein
la source
4
Un contre-argument est que si vous êtes un environnement intégré, vous - en tant que développeur - seriez beaucoup plus fastidieux dans votre gestion de la mémoire en premier lieu. Habituellement, c'est au point de pré-allouer réellement de la mémoire fixe statique à l'avance plutôt que d'avoir des mallocs / reallocs d'exécution du tout.
John Go-Soco
1
@lunarplasma: Bien que ce que vous dites ne soit pas incorrect, cela ne change pas le fait que la norme des langues énonce, et tous ceux qui agissent contre / font avancer, que ce soit par bon sens, produisent du code limité. Je peux comprendre si quelqu'un dit "je n'ai pas à m'en soucier", car il y a suffisamment de cas où c'est ok. MAIS que l'on devrait au moins savoir POURQUOI il n'a pas à s'en soucier. et surtout ne pas l'omettre tant qu'une question n'est pas liée à ce cas particulier. Et puisque OP pose des questions sur le C en général sous les aspects théoriques (scolaires). Ce n'est pas correct de dire "Vous n'en avez pas besoin"!
dhein
2
Dans la plupart des environnements où il n'y a pas de système d'exploitation, il n'y a aucun moyen par lequel les programmes peuvent "se terminer".
supercat
@supercat: Comme je l'ai déjà écrit: vous avez raison. Mais si quelqu'un pose des questions sur les raisons de l'enseignement et les aspects scolaires, il n'est pas juste de dire "Vous n'avez pas besoin d'y penser la plupart du temps, cela n'a pas d'importance" La formulation et le comportement de la langue la définition est donnée pour une raison, et juste parce que la plupart des environnements la gèrent pour vous, vous ne pouvez pas dire qu'il n'y a pas lieu de s'en soucier. C'est mon point.
dhein
2
-1 pour citer la norme C alors que la plupart ne s'applique pas en l'absence d'un système d'exploitation, car il n'y a pas d'exécution pour fournir les fonctionnalités des mandats standard, en particulier en ce qui concerne la gestion de la mémoire et les fonctions de bibliothèque standard (qui sont également évidemment absentes avec le runtime / OS).
23

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

Donc, si cela se produit, j'ai maintenant une fuite.

En ce qui concerne votre deuxième question, si mon programme prenait des entrées comme a = 5, j'allouerais de l'espace pour a, ou réallouerais le même espace sur un a = "foo" suivant. Cette somme resterait allouée jusqu'à:

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

Je ne peux penser à aucun système d' exploitation moderne qui ne récupère pas de mémoire après la fin d'un processus. Là encore, free () est bon marché, pourquoi ne pas nettoyer? Comme d'autres l'ont dit, des outils comme valgrind sont parfaits pour repérer les fuites dont vous avez vraiment besoin de vous inquiéter. Même si les blocs que vous représentez seraient étiquetés comme `` toujours accessibles '', 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 c'est dans main (), je n'ai pas à le libérer ", c'est incorrect. Considérer ce qui suit:

char *t;

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

Si cela arrivait avant de bifurquer / démoniser (et en théorie de fonctionner pour toujours), votre programme vient de fuir une taille indéterminée de t 255 fois.

Un bon programme bien écrit doit toujours se nettoyer après lui-même. Libérez toute la mémoire, videz tous les fichiers, fermez tous les descripteurs, dissociez tous les fichiers temporaires, etc. détecter un crash et reprendre.

Vraiment, soyez gentil avec la pauvre âme qui doit entretenir vos affaires lorsque vous passez à d'autres choses ... remettez-les-leur «valgrind clean» :)

Tim Post
la source
1
Et oui, un jour, un coéquipier m'a dit: "Je n'ai jamais besoin d'appeler free () in main ()" <shudders>
Tim Post
free() is cheapsauf si vous avez un milliard de structures de données avec une relation complexe que vous devez libérer une par une, traverser la structure de données pour essayer de tout libérer pourrait augmenter considérablement votre temps d'arrêt, surtout si la moitié de cette structure de données est déjà paginée sur le disque, sans aucun avantage.
Lie Ryan
3
@LieRyan Si vous avez un milliard, comme dans littéralement un milliard de structures, vous avez très certainement d'autres problèmes qui nécessitent un degré de considération spécialisé - bien au-delà de la portée de cette réponse particulière :)
Tim Post
13

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

Cela étant dit, l'une des raisons pour lesquelles les gens continuent d'insister sur le fait qu'il est bon de tout libérer avant de quitter est que les débogueurs de mémoire (par exemple, valgrind sur Linux) détectent les blocs non libérés comme des fuites de mémoire, et si vous avez également de "vraies" fuites de mémoire, cela devient plus difficile de les repérer si vous obtenez également de «faux» résultats à la fin.

Antti Huima
la source
1
Valgrind ne fait-il pas assez bien la distinction entre "fuite" et "toujours joignable"?
Christoffer
11
-1 pour "complètement bien" C'est une mauvaise pratique de codage de laisser la mémoire allouée sans la libérer. Si ce code était extrait dans une bibliothèque, cela provoquerait des fuites de mémoire partout.
DevinB
5
+1 pour compenser. Voir la réponse de compie. freeà exittemps considéré comme néfaste.
R .. GitHub ARRÊTEZ D'AIDER LA GLACE
11

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

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

Bill le lézard
la source
11

Ce code fonctionne généralement bien, mais considérez 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 manière que la mémoire est ensuite automatiquement récupérée. Semble bien.

Ensuite, quelqu'un d'autre copie votre extrait de code dans son projet de manière à ce 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. Habituellement, 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. Ainsi, en écrivant un code aussi "innocent", vous causez des maux de tête potentiels à d'autres personnes. Cela pourrait vous faire virer.

acéré
la source
2
Il peut être intéressant de noter la possibilité non seulement que quelqu'un copie l'extrait de code, mais également la possibilité d'un programme qui a été écrit pour effectuer une action particulière une fois modifié pour le faire à plusieurs reprises. Dans un tel cas, il serait bien que la mémoire soit allouée une fois puis utilisée à plusieurs reprises sans jamais être libérée, mais allouer et abandonner la mémoire pour chaque action (sans la libérer) pourrait être désastreux.
supercat
7

Quel est le vrai résultat ici?

Votre programme a fuit la mémoire. Selon votre système d'exploitation, il peut avoir été récupéré.

La plupart des modernes bureau des systèmes d'exploitation font récupérer la mémoire de fuite à la fin du processus, ce qui rend malheureusement commun d'ignorer le problème, comme on peut le voir par beaucoup d' autres réponses ici.)

Mais vous comptez sur une fonction de sécurité , vous ne devez pas compter sur, et votre programme (ou fonction) peut fonctionner sur un système où ce comportement ne fait une fuite de mémoire « dur », la prochaine fois.

Vous pouvez être exécuté en mode noyau, ou sur des systèmes d'exploitation vintage / intégrés qui n'utilisent pas de protection de mémoire comme compromis. (Les MMU occupent de l'espace, la protection de la mémoire coûte des cycles CPU supplémentaires, et ce n'est pas trop demander au programmeur de nettoyer après lui-même).

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

DevSolar
la source
5

Il y a en fait une section dans le manuel en ligne OSTEP pour un cours de premier cycle sur les systèmes d'exploitation qui traite exactement de votre question.

La section appropriée est «Oublier de libérer de la mémoire» dans le chapitre API de mémoire à la page 6 qui donne l'explication suivante:

Dans certains cas, il peut sembler raisonnable de ne pas appeler free (). Par exemple, votre programme est de courte durée et va bientôt se terminer; dans ce cas, à la fin du processus, le système d'exploitation nettoiera toutes ses pages allouées et donc aucune fuite de mémoire n'aura lieu en soi. Bien que cela "fonctionne" certainement (voir le côté à la page 7), c'est probablement une mauvaise habitude à développer, alors méfiez-vous de choisir une telle stratégie

Cet extrait s'inscrit dans le cadre de l'introduction du concept de mémoire virtuelle. Fondamentalement, à ce stade du livre, les auteurs expliquent que l'un des objectifs d'un système d'exploitation est de «virtualiser la mémoire», c'est-à-dire de faire croire à chaque programme qu'il a accès à un très grand espace d'adressage mémoire.

Dans les coulisses, le système d'exploitation traduira les «adresses virtuelles» que l'utilisateur voit en adresses réelles pointant vers la mémoire physique.

Cependant, le partage de ressources telles que la mémoire physique nécessite que le système d'exploitation garde trace des processus qui l'utilisent. Donc, si un processus se termine, il est dans les capacités et les objectifs de conception du système d'exploitation de récupérer la mémoire du processus afin qu'il puisse redistribuer et partager la mémoire avec d'autres processus.


EDIT: Le côté mentionné dans l'extrait est copié ci-dessous.

À CÔTÉ: POURQUOI AUCUNE MÉMOIRE N'EST FUITE UNE FOIS VOTRE PROCESSUS

Lorsque vous écrivez un programme de courte durée, vous pouvez allouer de l'espace en utilisant malloc(). Le programme s'exécute et est sur le point de se terminer: faut-il appeler plusieurs free()fois juste avant de quitter? Bien qu'il semble faux de ne pas le faire, aucune mémoire ne sera "perdue" dans un sens réel. La raison est simple: il y a vraiment deux niveaux de gestion de la mémoire dans le système. Le premier niveau de gestion de la mémoire est effectué par le système d'exploitation, qui distribue de la mémoire aux processus lors de leur exécution et la reprend lorsque les processus se terminent (ou meurent autrement). Le deuxième niveau de gestion se trouve dans chaque processus, par exemple dans le tas lorsque vous appelez malloc()et free(). Même si vous ne parvenez pas à appelerfree()(et donc une fuite de mémoire dans le tas), le système d'exploitation récupérera toute la mémoire du processus (y compris les pages pour le code, la pile et, selon le cas ici, le tas) lorsque le programme est terminé en cours d'exécution. Quel que soit l'état de votre tas dans votre espace d'adressage, le système d'exploitation reprend toutes ces pages à la fin du processus, garantissant ainsi qu'aucune mémoire n'est perdue malgré le fait que vous ne l'ayez pas libérée.

Ainsi, pour les programmes de courte durée, les fuites de mémoire ne provoquent souvent aucun problème de fonctionnement (même si elles peuvent être considérées comme de mauvaise forme). Lorsque vous écrivez un serveur de longue durée (comme un serveur Web ou un système de gestion de base de données, qui ne se ferme jamais), la fuite de mémoire est un problème beaucoup plus important et entraînera éventuellement un plantage lorsque l'application manque de mémoire. Et bien sûr, la fuite de mémoire est un problème encore plus important dans un programme particulier: le système d'exploitation lui-même. Nous montrant encore une fois: ceux qui écrivent le code du noyau ont le travail le plus difficile de tous ...

de la page 7 du chapitre API mémoire de

Systèmes d'exploitation: trois pièces faciles
Remzi H. Arpaci-Dusseau et Andrea C. Arpaci-Dusseau Arpaci-Dusseau Books Mars 2015 (Version 0.90)

spenceryue
la source
4

Il n'y a pas de réel danger à ne pas libérer vos variables, mais si vous affectez un pointeur à un bloc de mémoire à un autre bloc de mémoire sans libérer le premier bloc, le premier bloc n'est plus accessible mais prend toujours de la place. C'est ce qu'on appelle une fuite de mémoire, et si vous le faites régulièrement, votre processus commencera à consommer de plus en plus de mémoire, supprimant les ressources système des autres processus.

Si le processus est de courte durée, vous pouvez souvent vous en passer, car toute la mémoire allouée est récupérée par le système d'exploitation à la fin du processus, mais je vous conseillerais de prendre l'habitude de libérer toute la mémoire dont vous n'avez plus besoin.

Kyle Cronin
la source
1
Je veux dire -1 pour votre première déclaration "il n'y a pas de danger" sauf que vous donnez ensuite une réponse réfléchie sur les raisons pour lesquelles IL Y A danger.
DevinB
2
Au fur et à mesure que les dangers disparaissent, c'est assez bénin - je prendrai une fuite de mémoire sur un défaut de segmentation un jour.
Kyle Cronin
1
Très vrai, et nous préférerions tous les deux ni l'un ni l'autre = D
DevinB
2
@KyleCronin Je beaucoup ai plutôt segfault qu'une fuite de mémoire, car les deux sont des bogues sérieux et segfaults sont plus faciles à détecter. Trop souvent, les fuites de mémoire passent inaperçues ou ne sont pas résolues car elles sont «assez bénignes». Ma RAM et moi sommes de tout cœur en désaccord.
Dan Bechard
@Dan En tant que développeur, bien sûr. En tant qu'utilisateur, je prendrai la fuite de mémoire. Je préfère avoir un logiciel qui fonctionne, mais avec une fuite de mémoire, plutôt qu'un logiciel qui ne fonctionne pas.
Kyle Cronin
3

Vous avez absolument raison à cet égard. Dans les petits programmes triviaux où une variable doit exister jusqu'à la mort du programme, il n'y a aucun avantage réel à désallouer la mémoire.

En fait, j'avais déjà été impliqué dans un projet où chaque exécution du programme était très complexe mais relativement de courte durée, et la décision était de garder la mémoire allouée et de ne pas déstabiliser le projet en faisant des erreurs en le désallouant.

Cela étant dit, dans la plupart des programmes, ce n'est pas vraiment une option, ou cela peut vous conduire à manquer de mémoire.

Uri
la source
2

Vous avez raison, la mémoire est automatiquement libérée à la fin du processus. Certaines personnes s'efforcent de ne pas effectuer de nettoyage approfondi lorsque le processus est terminé, car tout sera abandonné au système d'exploitation. Cependant, pendant l'exécution de votre programme, vous devez libérer la mémoire inutilisée. Si vous ne le faites pas, vous pouvez éventuellement vous épuiser ou provoquer une pagination excessive si votre ensemble de travail devient trop gros.

Michael
la source
2

Si vous développez une application à partir de zéro, vous pouvez faire des choix éclairés sur le moment d'appeler gratuitement. Votre exemple de programme est très bien: il alloue de la mémoire, peut-être que vous le faites fonctionner pendant quelques secondes, puis se ferme, libérant toutes les ressources qu'il a revendiquées.

Si vous écrivez autre chose - un serveur / une application longue durée ou une bibliothèque à utiliser par quelqu'un d'autre, vous devez vous attendre à appeler gratuitement tout ce que vous malloc.

Ignorant le côté pragmatique pendant une seconde, il est beaucoup plus sûr de suivre l'approche plus stricte et de vous forcer à libérer tout ce que vous malloc. Si vous n'avez pas l'habitude de surveiller les fuites de mémoire chaque fois que vous codez, vous pouvez facilement provoquer quelques fuites. Donc, en d'autres termes, oui - vous pouvez vous en sortir sans elle; s'il vous plaît soyez prudent, cependant.

ojrac
la source
0

Si un programme oublie de libérer quelques mégaoctets avant de quitter, le système d'exploitation les libérera. Mais si votre programme s'exécute pendant des semaines à la fois et qu'une boucle à l'intérieur du programme oublie de libérer quelques octets à chaque itération, vous aurez une puissante fuite de mémoire qui consommera toute la mémoire disponible sur votre ordinateur, sauf si vous la redémarrez régulièrement. base => même de petites fuites de mémoire peuvent être mauvaises si le programme est utilisé pour une tâche sérieuse, même s'il n'a pas été conçu à l'origine pour une seule.

Gunter Königsmann
la source
-2

Je pense que vos deux exemples ne sont en fait qu'un seul: le free()ne devrait se produire qu'à la fin du processus, ce qui, comme vous le signalez, est inutile car le processus se termine.

Dans votre deuxième exemple, la seule différence est que vous autorisez un nombre indéfini de malloc(), ce qui pourrait entraîner un manque de mémoire. La seule façon de gérer la situation est de vérifier le code retour de malloc()et d'agir en conséquence.

mouviciel
la source