web-dev-qa-db-fra.com

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

Cela me préoccupe depuis des lustres.

On nous enseigne tous à l'école (du moins c'était le cas) que vous DEVEZ libérer chaque pointeur attribué. Je suis cependant un peu curieux du coût réel de la non libération de mémoire. Dans des cas évidents, comme lorsque malloc est appelé dans une boucle ou dans l'exécution d'un thread, il est très important de libérer afin d'éviter toute fuite de mémoire. Mais considérons les deux exemples suivants:

Premièrement, si j'ai un code qui ressemble à 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? Ma pensée est que le processus meurt et qu'ensuite l'espace libre est perdu, il n'y a donc aucun mal à rater l'appel à free (cependant, je reconnais qu'il est important de l'avoir de toute façon 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 telles que aaa = 123. Celles-ci sont stockées dans une structure de données dynamique pour une utilisation ultérieure. Clairement, 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, il n’a pas de 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’ya pas de bonne façon (que je puisse voir) de l’implémenter avec un espace alloué de manière statique. 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?

470
Scott

À peu près tous les systèmes d'exploitation modernes vont récupérer tout l'espace mémoire alloué après la sortie du programme. La seule exception à laquelle je peux penser est peut-ê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. Par conséquent, ne pas libérer le programme risque de surcharger le programme. (Je ne fais que spéculer ici.)

En règle générale, il n’ya donc aucun inconvénient, à l’exception du coût d’exécution du stockage supplémentaire dont vous avez besoin. Dans l'exemple que vous donnez, vous souhaitez certainement conserver la mémoire d'une variable pouvant ê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 consistant à connaître la mémoire que vous utilisez et à déterminer si vous en avez toujours besoin. Si vous ne gardez pas le suivi, vous pourriez avoir des fuites de mémoire.

D'autre part, la recommandation similaire 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 risquent de ne pas disparaître. être supprimé lorsque vous avez terminé. En outre, les transactions des descripteurs de base de données doivent être validées puis fermées lorsque vous avez terminé. 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é avec cela signifiera que le destructeur ne sera jamais appelé et que les ressources dont la classe est responsable risquent de ne pas être nettoyées.

328
Paul Tomblin

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: Mythes sur l'allocation et le GC (alerte PostScript!)

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

La vérité: omis désallocations fréquemment exécutées code provoquer des fuites croissantes. Elles sont rarement acceptable. mais les programmes qui conserve la plupart de la mémoire allouée jusqu'à les sorties de programmes fonctionnent souvent mieux sans aucune désallocation intervenante . Malloc est beaucoup plus facile à implémenter si il n'y a pas de libre.

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

Conséquence: soyez prudent avec les "détecteurs de fuite." Qui comptent les affectations . Certaines "fuites" sont bonnes!

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

Deuxième question: votre design est ok. Si vous devez stocker quelque chose jusqu'à la fermeture de votre application, vous pouvez le faire avec une allocation dynamique de la mémoire. Si vous ne connaissez pas la taille requise dès le départ, vous ne pouvez pas utiliser de mémoire allouée de manière statique.

100
compie

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

Si vous non écrivez le code pour libérer les objets, vous le limitez à une utilisation sûre lorsque vous pouvez compter sur la mémoire libérée par le processus en cours de fermeture ... petits projets à usage unique ou "jetables"[1] projets) ... où vous savez quand le processus se terminera.

Si vous do écrivez le code qui libère toute la mémoire allouée dynamiquement par free (), vous corrigez le code pour le futur et laissez les autres l’utiliser dans un projet plus volumineux.


[1] concernant les projets "jetables". Le code utilisé dans les projets "Throw-away" a un moyen de ne pas être jeté. Vous savez maintenant que dix ans se sont écoulés et que votre code "jetable" est toujours utilisé).

J'ai entendu parler d'un type qui a écrit du code juste pour le plaisir, afin que son matériel fonctionne mieux. Il a dit " juste un passe-temps, ne sera pas grand et professionnel ". Des années plus tard, beaucoup de gens utilisent son code "passe-temps".

52
Trevor Boyd Smith

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 postes de travail et de serveurs libèrent simplement l’espace mémoire total à la sortie (). Ils ne sont pas au courant des structures de données internes au programme telles que les tas.

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

  • Plus important encore, c’est une perte de temps quand on le fait 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 free () consomme le temps processeur et peut entraîner des opérations de pagination de disque, des erreurs de cache et des expulsions de cache.

En ce qui concerne la possibilité de la 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 méthode Agile . YAGNI!

46
DigitalRoss

En général, je libère chaque bloc alloué une fois que j'en suis certain. Aujourd'hui, le point d'entrée de mon programme pourrait être main(int argc, char *argv[]), mais demain, il pourrait s'agir de foo_entry_point(char **args, struct foo *f) et être saisi en tant que 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 une entrée comme un = 5, j'attribuerais de l'espace pour un, ou réaffecter le même espace à un prochain a = "foo". Cela resterait alloué jusqu'à:

  1. L'utilisateur a tapé 'unset a'
  2. Ma fonction de nettoyage a été entrée, soit en desservant un signal, soit en tapant "quitter"

Je ne peux penser à aucun moderne OS qui ne récupère pas la mémoire après la fin d'un processus. Encore une fois, free () est bon marché, pourquoi ne pas nettoyer? Comme d'autres l'ont déjà dit, des outils tels que Valgrind sont parfaits pour détecter les fuites dont vous avez réellement besoin. Même si les blocs que vous indiquez sont étiquetés comme «toujours accessibles», il s'agit simplement d'un bruit supplémentaire dans la sortie lorsque vous essayez de vous assurer qu'il n'y a pas de fuite.

Un autre mythe est " Si c'est dans main (), je n'ai pas à le libérer ", c'est inexact. 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 le démodelage/démonérisation (et en théorie, il fonctionnait indéfiniment), votre programme venait de perdre une taille indéterminée de t 255 fois.

Un bon programme bien écrit doit toujours être nettoyé. Libérer toute la mémoire, vider tous les fichiers, fermer tous les descripteurs, dissocier 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 à autre chose .. remettez-leur-les 'valgrind clean' :)

22
Tim Post

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

Tout le monde parle d'un système d'exploitation moderne et/ou hérité.

Mais que se passe-t-il si je suis dans un environnement où je n’ai tout simplement pas d’OS?? Où il n’ya rien?

Imaginez maintenant que vous utilisez des interruptions de style thread 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 suit:

7.20.3 Fonctions de gestion de la mémoire

1 L'ordre et la contiguïté de la mémoire allouée par des appels successifs au calloc, malloc, et les fonctions realloc ne sont pas spécifiées. Le pointeur est retourné si l'allocation success est correctement aligné de sorte qu'il puisse être affecté à un pointeur sur tout type d'objet et ensuite 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'allocation jusqu'à la désallocation. [...]

Il n’est donc pas nécessaire de savoir que l’environnement fait le travail de libération à votre place ... sinon, il serait ajouté à la dernière phrase: "Ou jusqu’à ce que le programme se termine."

En d'autres termes: Ne pas libérer de mémoire n'est pas simplement une mauvaise pratique. Il produit un code non portable et non conforme à C .. .. Ce qui peut au moins être considéré comme "correct, si les conditions suivantes sont remplies: [...], est pris en charge par l'environnement".

Mais dans les cas où vous n’avez pas du tout de système d’exploitation, personne ne fait le travail à votre place .__ (généralement, vous n’allouez pas et ne réallouez pas la mémoire sur des systèmes embarqués, Mais il y a des cas où vous voudrez peut-être .)

Donc, parlant en général plaine C (comme quoi le PO est étiqueté), cela produit simplement un code erroné et non portable.

19
dhein

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

Cela dit, si les gens insistent toujours pour dire qu'il est bon de tout libérer avant de quitter, c'est que les débogueurs de mémoire (par exemple, valgrind sous Linux) détectent les blocs non loués comme des fuites de mémoire. plus difficile de les repérer si vous obtenez également des résultats "faux" à la fin.

12
Antti Huima

Ce code fonctionnera généralement normalement, 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 manière à ce que la mémoire soit automatiquement récupérée. Semble bien.

Ensuite, quelqu'un d'autre copie votre extrait 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. Habituellement, l’entreprise possède tout le code produit par ses employés et chaque service peut réutiliser ce que l’entreprise possède. Donc, en écrivant un tel code "à l'air innocent", vous causez des maux de tête potentiels à d'autres personnes. Cela peut vous faire virer.

11
sharptooth

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 principale) qui allouent de la mémoire sans la libérer ni la mettre à la disposition du reste de votre programme. Ensuite, votre programme continue de fonctionner avec la mémoire allouée, mais aucun moyen de l’utiliser. Votre programme et d’autres programmes en cours d’exécution sont privés de cette mémoire.

Edit: Il n'est pas précis à 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 utiliser aux dépens du transfert de votre programme en mémoire virtuelle (</handwaving>). Cependant, le fait est que si votre programme libère de la mémoire qu'il n'utilise pas, un échange de mémoire virtuelle sera probablement moins nécessaire.

11
Bill the Lizard

Quel est le vrai résultat ici?

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

La plupart des ordinateurs de bureaumodernes _ systèmes d'exploitation do récupèrent la mémoire perdue à la fin du processus, ce qui rend tristement commun d'ignorer le problème, comme le montrent de nombreuses autres réponses ici.)

Mais vous comptez sur une fonctionnalité de sécurité sur laquelle vous ne devriez pas compter, et votre programme (ou fonction) peut s'exécuter sur un système où ce comportement ne entraîne une fuite de mémoire "dure", next temps.

Vous pouvez être exécuté en mode noyau ou sur des systèmes d’exploitation vintage/incorporé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 calcul supplémentaires et il n’est pas excessif de demander au programmeur de nettoyer 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.

5
DevSolar

Il existe en fait une section dans le manuel en ligne OSTEP pour un cours de premier cycle en systèmes d’exploitation qui traite précisément de votre question.

La section appropriée est "Oublier la mémoire libre" dans le chapitre API de mémoire à la page 6, qui donne l'explication suivante:

Dans certains cas, il peut sembler que ne pas appeler free () est raisonnable. Pour Par exemple, votre programme est de courte durée et finira bientôt; dans ce cas, lorsque le processus est terminé, le système d'exploitation nettoie toutes les pages qui lui sont allouées et aucune fuite de mémoire ne se produira en soi. travaux" (voir à la page 7), c’est probablement une mauvaise habitude à prendre, alors méfiez-vous de choisir une telle stratégie

Cet extrait se situe dans le contexte de l'introduction du concept de mémoire virtuelle. À ce stade du livre, les auteurs expliquent qu'un des objectifs d'un système d'exploitation est de "virtualiser la mémoire", c'est-à-dire de laisser chaque programme croire qu'il a accès à un très grand espace d'adressage mémoire.

En coulisse, le système d'exploitation traduit 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 la trace des processus qui l'utilisent. Ainsi, si un processus se termine, les capacités et les objectifs de conception du système d'exploitation lui permettent de récupérer la mémoire du processus afin qu'il puisse être redistribué et partagé avec d'autres processus.


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

EN OUTRE: POURQUOI PAS DE MÉMOIRE IS FUITE UNE FOIS VOTRE PROCESSUS SORTIE

Lorsque vous écrivez un programme de courte durée, vous pouvez allouer de l’espace en utilisant malloc(). Le programme fonctionne et est sur le point de se terminer: y at-il besoin d'appeler free() plusieurs fois avant de quitter? Bien qu'il semble mal de ne pas, 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 remet de la mémoire aux processus quand ils sont exécutés et les récupère quand processus sort (ou meurt autrement). Le deuxième niveau de gestion est dans chaque processus, par exemple dans le tas lorsque vous appelez malloc() et free(). Même si vous ne parvenez pas à appeler free() (et perdez ainsi de la mémoire Dans le tas), le système d'exploitation récupérera toute la mémoire de le processus (y compris les pages relatives au code, à la pile et, le cas échéant, au heap) lorsque l'exécution du programme est terminée. Peu importe ce que l'état de votre segment de mémoire dans votre espace d'adressage, le système d'exploitation reprend toutes ces pages quand le processus meurt, assurant ainsi qu'aucune mémoire n'est perdue malgré le fait que vous ne l’ayez pas libéré.

Ainsi, pour les programmes éphémères, les fuites de mémoire ne causent souvent aucun problèmes opérationnels (bien que cela puisse être considéré comme une forme médiocre). Quand Si vous écrivez un serveur de longue durée (tel qu'un serveur Web ou un système de gestion de base de données qui ne se ferme jamais), une fuite de mémoire est un problème beaucoup plus important, et finira par provoquer un crash lorsque l'application sera épuisée Mémoire. Et bien sûr, les fuites de mémoire sont un problème encore plus important à l’intérieur un programme particulier: le système d'exploitation lui-même. Nous montrer une fois encore une fois: ceux qui écrivent le code du noyau ont le travail le plus difficile qui soit ...

tiré de la page 7 sur API de mémoire chapitre de

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

4
spenceryue

Il n'y a pas de danger réel à ne pas libérer vos variables, mais si vous assignez un pointeur à un bloc de mémoire sur un bloc de mémoire différent 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 avec régularité, votre processus consommera de plus en plus de mémoire, privant ainsi les ressources système d'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 pour laquelle vous n'avez plus aucune utilisation.

3
Kyle Cronin

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 courte, et la décision était simplement de laisser la mémoire allouée et non de déstabiliser le projet en faisant des erreurs pour le désallouer. 

Cela dit, dans la plupart des programmes, ce n’est pas vraiment une option, sinon cela peut vous amener à manquer de mémoire. 

3
Uri

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 à la fin du processus, car toutes les tâches seront abandonnées au système d'exploitation. Cependant, pendant que votre programme est en cours d'exécution, vous devez libérer de la mémoire inutilisée. Sinon, vous risquez de vous épuiser ou de provoquer une pagination excessive si votre poste de travail devient trop volumineux.

2
Michael

Si vous développez une application à partir de rien, vous pouvez choisir en toute connaissance de cause quand appeler gratuitement. Votre exemple de programme est correct: il alloue de la mémoire, vous l’aurez peut-être travailler pendant quelques secondes, puis se ferme, libérant ainsi toutes les ressources qu’il revendiquait.

Si vous écrivez quelque chose d'autre, un serveur/une application de longue durée, ou une bibliothèque pouvant être utilisée par quelqu'un d'autre, vous devez vous attendre à appeler gratuitement sur tout ce que vous malloc.

Ignorant le côté pragmatique pendant une seconde, il est beaucoup plus prudent 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 passer; s'il vous plaît soyez prudent, cependant.

2
ojrac

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 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 à moins que vous ne le redémarriez régulièrement. BASE => Même les petites fuites de mémoire peuvent être mauvaises si le programme est utilisé pour une tâche très lourde, même s'il n'avait pas été conçu à l'origine.

0
Gunter Königsmann