Que se passe-t-il quand on ne se libère pas après malloc?
C'est quelque chose qui me dérange depuis des lustres.
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 la mémoire. Dans certains cas évidents, comme lorsque malloc
est appelé à l'intérieur d'une boucle ou d'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érez les deux exemples suivants:
tout D'abord, si j'ai un 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? Ma pensée est que le processus meurt et puis l'espace de tas est parti de toute façon donc il n'y a aucun mal à manquer l'appel à free
(cependant, je reconnais l'importance de l'avoir de toute façon pour la fermeture, la maintenabilité, et la bonne pratique). Suis-je le droit dans cette réflexion?
Deuxième, disons que j'ai un programme qui agit un peu comme une coquille. Les utilisateurs peuvent déclarer des variables comme aaa = 123
et 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 appellerait une fonction *alloc (hashmap, liste liée, quelque chose comme ça). Pour ce genre de programme, il n'est pas logique de libérer jamais après avoir appelé malloc
parce que ces variables doivent être présentes à tout moment pendant l'exécution du programme et il n'y a pas de bonne façon (que je peux voir) pour mettre en œuvre ceci avec espace statiquement alloué. Est-ce une mauvaise conception 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?
17 réponses
à peu près chaque système d'exploitation moderne récupérera tout l'espace mémoire alloué après la sortie 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 ne pas libérer pourrait causer le programme de prendre plus de stockage. (Je ne fais que spéculer ici.)
donc, en général, il n'y a aucun mal à cela, sauf le coût de fonctionnement d'avoir plus de stockage que vous avez besoin. Certainement dans l'exemple que vous donnez, vous voulez garder la mémoire pour une variable qui peut être utilisée jusqu'à ce qu'elle est désactivée.
cependant, il est considéré comme un bon style pour la mémoire libre dès que vous n'en avez plus besoin, et pour libérer tout ce que vous avez encore autour de la sortie du programme. C'est plus un exercice de savoir quelle mémoire vous utilisez, et de penser à savoir si vous en avez encore besoin. Si vous ne gardez pas de trace, vous pourriez avoir des fuites de mémoire.
sur l'autre hand, l'admonition similaire pour fermer vos fichiers à la sortie a un résultat beaucoup plus concret - si vous ne le faites pas, les données que vous avez écrit à eux pourraient ne pas obtenir flushed, ou si ils sont un fichier temporaire, ils pourraient ne pas être supprimés lorsque vous avez terminé. En outre, la base de données handles devrait avoir leurs transactions engagées et ensuite fermé lorsque vous avez terminé avec eux. De la même façon, si vous utilisez un langage orienté objet comme C++ ou Objectif C, ne pas libérer un objet quand vous en avez fini avec lui signifiera que le destructeur jamais, et toutes les ressources de la classe est responsable pourrait ne pas obtenir nettoyé.
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é.
Source: l'Allocation et la GC Mythes (PostScript alerte!)
mythe D'attribution 4: programmes ne recueillant pas les ordures devrait toujours désallouer toute la mémoire ils allouent.
La Vérité: Omise désallocations dans les cas d'exécution fréquente code cause des fuites croissantes. Ils sont rarement acceptable. mais les programmes qui conserver la mémoire la plus allouée jusqu'à la sortie du programme sont souvent meilleurs sans aucune intervention de libération de la mémoire. Malloc est beaucoup plus facile à mettre en œuvre si il n'y a rien de gratuit.
dans la plupart des cas, deallocating memory juste avant la sortie du programme est inutile. L'OS le récupérera de toute façon. Gratuit sera le toucher et la page dans la mort objets; le système D'exploitation ne le fera pas.
conséquence: être prudent avec " fuite les détecteurs " qui comptent les attributions. Certaines "fuites" sont bons!
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'à la sortie de votre application, alors c'est ok de le faire avec l'allocation de mémoire dynamique. Si vous ne connaissez pas la taille requise à l'avance, vous ne pouvez pas utiliser la mémoire statiquement allouée.
= = = Qu'en est-il de future proofing et code reuse ? = = =
si vous don't écrivez le code pour libérer les objets, alors vous limitez le code à n'être sûr à utiliser que lorsque vous pouvez dépendre de la mémoire étant libre par le processus étant fermé ... c'est-à-dire de petits projets à usage unique ou "jetés"" [1] projets)... où vous savez quand le processus prendra fin.
si vous do écrivez le code que libre()est toute votre mémoire dynamiquement attribuée, alors vous êtes à l'avenir la mise à l'épreuve du code et de laisser d'autres l'utiliser dans un plus grand projet.
[1] concernant les projets "jetés". Le Code utilisé dans les projets "jetés" a une façon de ne pas être jeté. La prochaine chose que vous savez dix années ont passé et votre "jeter" code est encore utilisé).
j'ai entendu une histoire à propos d'un gars qui a écrit un code juste pour le plaisir de faire son matériel fonctionne mieux. Il a dit " juste un hobby, ne sera pas grand et professionnel ". Des années plus tard, beaucoup de gens utilisent son code "hobby".
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 de serveur libèrent simplement la totalité de l'espace mémoire sur exit(). Ils ne sont pas au courant des structures de données internes du programme, comme les tas.
-
presque toutes
free()
implémentations ne jamais retour à la mémoire le système d'exploitation de toute façon. -
plus important encore, c'est une perte de temps quand c'est fait juste avant la sortie(). À la sortie, les pages mémoire et l'espace de pagination sont tout simplement libérés. Par contraste, une série d'appels gratuits() brûlera le temps CPU et peut entraîner des opérations de pagination de disque, des erreurs de cache, et des expulsions de cache.
concernant la possibilité de réemploi futur de code justifiant la certitude d'ops inutiles: c'est une considération mais ce n'est sans doute pas la Agile voie. YAGNI!
je libère généralement chaque bloc alloué une fois que je suis sûr que j'en ai fini avec elle. 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 arrive, j'ai maintenant une fuite.
en ce qui concerne votre deuxième question, si mon programme prenait des entrées comme a=5, j'attribuerais de l'espace pour a, ou réattribuerais le même espace sur A="foo" subséquent. Cela resterait alloué jusqu'au:
- L'utilisateur a tapé 'annuler'
- ma fonction de nettoyage a été saisie, soit l'entretien d'un signal, soit l'Utilisateur a tapé "quit "
je ne peux pas penser à tout moderne système d'exploitation qui ne permet pas de récupérer de la mémoire après un processus s'arrête. Encore une fois, 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 devez vraiment vous soucier. Même si les blocs de votre exemple seraient étiquetés comme 'toujours accessibles' , c'est juste un bruit supplémentaire dans la sortie quand vous essayez de vous assurer que vous n'avez pas de fuites.
un autre mythe est si son dans le main (), Je ne dois pas le libérer ", c'est incorrect. Considérons ce qui suit:
char *t;
for (i=0; i < 255; i++) {
t = strdup(foo->name);
let_strtok_eat_away_at(t);
}
si cela est venu avant de forking / daemonizing (et en théorie courir pour toujours), votre programme vient de fuiter une taille indéterminée de T 255 temps.
un bon programme, bien écrit devrait toujours nettoyer après lui-même. Gratuit toute la mémoire, vider tous les fichiers, fermer tous les descripteurs, de rompre tous les liens des fichiers temporaires, etc. Cette fonction de nettoyage doit être atteinte lors de la terminaison normale, ou lors de la réception de divers types de signaux mortels, à moins que vous ne vouliez laisser des fichiers traîner afin que vous puissiez détecter un crash et reprendre.
vraiment, soyez gentil avec la pauvre âme qui doit entretenir vos affaires quand vous vous déplacez sur d'autres choses .. la main à "valgrind propre":)
Je ne suis pas du tout d'accord avec tous ceux qui disent que L'OP est correcte ou qu'il n'y a pas de mal.
tout le monde parle D'OS modernes et/ou hérités.
mais que faire si je suis dans un environnement où je n'ai tout simplement pas D'OS? Où il n'y a pas quelque chose?
imaginez maintenant que vous utilisez des interruptions de style thread et allouer la mémoire. Dans la norme C ISO / CEI: 9899 est la durée de vie de la mémoire déclaré comme:
7.20.3 fonctions de gestion de la mémoire
1 l'ordre et la contiguïté du stockage attribué par appels successifs à la calloc, malloc, et realloc fonctions est indéterminée. Le pointeur retourné si la répartition succeeds est convenablement aligné de sorte qu'il puisse être assigné à un pointeur sur n'importe quel type d'objet et ensuite utilisé pour accéder à un tel objet ou un tableau de ces objets dans l'espace alloué (jusqu'à ce que l'espace est explicitement libéré). La durée de vie d'une objet attribué s'étend de la répartition jusqu'à la répartition.[...]
donc il ne faut pas donner que l'environnement fait le travail libérateur pour vous. Sinon, il serait ajouté à la dernière phrase: "ou jusqu'à la fin du programme."
donc en d'autres termes: Ne pas libérer la mémoire n'est pas seulement une mauvaise pratique. Il produit du code non portable et non conforme à C. Qui peut au moins être considérée comme correcte, si les conditions suivantes: [...], être pris en charge par l'environnement".
mais dans les cas où vous n'avez pas D'OS du tout, personne ne fait le travail pour vous (Je sais qu'en général, vous n'allouez pas et ne réallouez pas la mémoire sur les systèmes embarqués, mais il ya des cas où vous voudrez.)
Donc, en général plaine C (que l'OP est balisé), il s'agit simplement de produire du code erroné et non portable.
il est tout à fait normal de laisser la mémoire vide 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é lorsque le processus sort.
cela dit, une des raisons pour lesquelles les gens insistent encore 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 sur Linux) détectent les blocs non créés comme des fuites de mémoire, et si vous avez aussi des fuites "réelles" de mémoire, il devient plus difficile de les repérer si vous obtenez également des résultats "faux" à la fin.
si vous utilisez la mémoire que vous avez attribuée, alors vous ne faites rien de mal. Cela devient un problème lorsque vous écrivez des fonctions (autres que main) qui allouent la mémoire sans la libérer, et sans la rendre disponible au reste de votre programme. Alors votre programme continue de fonctionner avec cette mémoire, mais pas moyen de l'utiliser. Votre programme et d'autres programmes en cours d'exécution sont privés de cette mémoire.
Edit: ce n'est pas 100% précis pour dire que d'autres programmes en cours d'exécution sont privés de cette mémoire. Le système d'exploitation peut toujours leur laisser l'utiliser au détriment de l'échange de votre programme dans la mémoire virtuelle ( </handwaving>
). Le point 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.
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 alors automatiquement récupérée. Semble bon.
alors quelqu'un d'autre copie votre fragment dans son projet de telle manière 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 dans général, généralement fatal pour une application serveur.
La réutilisation du Codeest typique dans les entreprises. En général, l'entreprise possède tout le code produit par ses employés et chaque service peut réutiliser ce qu'elle possède. Donc, en écrivant un tel code "d'apparence innocente", vous causez des maux de tête potentiels à d'autres personnes. Cela peut te faire virer.
Quel est le résultat?
votre programme a fait fuiter la mémoire. Selon votre OS, il mai ont été récupérés.
"151910920 Plus" moderne bureau systèmes d'exploitation faire récupérer la mémoire perdue à la fin du processus, le rendant malheureusement commun à ignorer le problème, comme on peut le voir par de nombreux autres réponses ici.)mais vous comptez sur un dispositif de sécurité sur lequel vous ne devriez pas compter, et votre programme (ou fonction) pourrait fonctionner sur un système où ce comportement fait résultant en une fuite de mémoire" dure", suivant heure.
vous pourriez être en mode noyau, ou sur des systèmes d'exploitation Vintage / embarqués qui n'utilisent pas la protection mémoire comme compromis. (Les MMU prennent de l'espace de mémoire, la protection de la mémoire coûte des cycles CPU supplémentaires, et il n'est pas trop demander à un programmeur pour le nettoyage après lui-même).
vous pouvez utiliser et réutiliser la mémoire comme vous le souhaitez, mais assurez-vous que vous avez désactivé toutes les ressources avant de quitter.
il n'y a pas de vrai danger en ne libérant pas vos variables, mais si vous assignez 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 tout de même de l'espace. C'est ce qu'on appelle une fuite de mémoire, et si vous faites cela régulièrement, alors votre processus va commencer à consommer de plus en plus de mémoire, système de ressources provenant d'autres processus.
si la le processus est de courte durée, vous pouvez souvent s'en sortir en faisant ce que la mémoire allouée est récupérée par le système d'exploitation lorsque le processus est terminé, mais je vous conseille d'obtenir dans l'habitude de libérer toute la mémoire, vous n'aurez plus l'usage.
vous avez raison, la mémoire est automatiquement libérée lorsque le processus est terminé. Certaines personnes s'évertuent à ne pas faire de nettoyage approfondi lorsque le processus est terminé, car tout sera abandonné au système d'exploitation. Cependant, pendant que votre programme tourne, vous devriez libérer la mémoire inutilisée. Si vous ne le faites pas, vous pourriez éventuellement vous épuiser ou causer un bippage excessif si votre ensemble de travail devient trop grand.
si vous développez une application à partir de zéro, vous pouvez faire des choix éclairés sur quand appeler gratuitement. Votre programme d'exemple est parfait: il alloue de la mémoire, peut-être que vous l'avez fait fonctionner pendant quelques secondes, puis ferme, libérant toutes les ressources qu'il réclame.
si vous écrivez autre chose, bien que -- un serveur/application de longue durée, ou une bibliothèque à utiliser par quelqu'un d'autre, vous devriez vous attendre à appeler gratuitement sur 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 pourriez facilement déclencher quelques fuites. Donc, en d'autres mots, OUI -- vous pouvez vous en sortir sans elle; faites attention, cependant.
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 pas d'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 de durée relativement courte, et la décision était simplement de garder la mémoire allouée et de ne pas déstabiliser le projet en faisant des erreurs de désallocation.
Cela étant dit, dans la plupart des programmes, ce n'est pas vraiment une option, ou elle peut vous amener à manquer de mémoire.
il y a en fait une section dans le OSTEP manuel en ligne pour un cours de premier cycle dans les systèmes d'exploitation qui discute exactement votre question.
la section pertinente est "oubli de 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 raisonnable de ne pas appeler free (). Pour exemple, votre programme est de courte durée, et va bientôt sortir; dans ce cas, lorsque le processus meurt, L'OS nettoie toutes ses pages allouées et donc pas de fuite de mémoire aura lieu en soi. bien que cela fonctionne certainement" (voir le côté de la page 7), c'est probablement une mauvaise habitude à prendre, donc méfiez-vous de choisir une telle stratégie
Cet extrait est dans le contexte de l'introduction du concept de mémoire virtuelle. Fondamentalement, à ce point dans le livre, les auteurs expliquent que l'un des objectifs d'un système d'exploitation est de "virtualiser mémoire", qui est, laisser à chaque programme de croire 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 vers les adresses réelles pointant vers la mémoire physique.
cependant, partager des ressources telles que la mémoire physique nécessite le système d'exploitation pour garder une trace des processus qui l'utilisent. Donc, si un processus se termine, alors 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 passage mentionné dans l'extrait est copié ci-dessous.
À PART: POURQUOI AUCUN SOUVENIR N'EST DIVULGUÉ UNE FOIS VOTRE PROCÉDÉS DE SORTIE
quand vous écrivez un programme de courte durée, vous pourriez allouer un peu d'espace en utilisant
malloc()
. Le programme s'exécute et est sur le point de terminer: besoin d'appelerfree()
plusieurs fois juste avant de partir? Alors qu'il semble pas, à tort, pas de mémoire sera "perdu" dans un sens réel. La raison en 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 réalisé par L'OS, qui donne de la mémoire aux processus quand ils courent, et la reprend quand les processus de sortie (ou mourir). Le deuxième niveau de gestion est dans chaque processus, par exemple dans le tas lorsque vous appelezmalloc()
etfree()
. Même si vous ne pouvez pas appelerfree()
(et ainsi de fuite de mémoire dans le tas), le système d'exploitation va récupérer toute la mémoire de le processus (y compris ces pages pour le code, la pile, et, le cas échéant ici, heap) lorsque le programme est terminé. Peu importe ce que l'état de votre tas dans votre espace d'adresse, L'OS reprend toutes ces pages lorsque le processus meurt, assurant ainsi qu'aucun souvenir n'est perdu en dépit de fait que vous n'avez pas libre.ainsi, pour les programmes de courte durée, la fuite de la mémoire ne provoque souvent pas problèmes opérationnels (bien qu'ils puissent être considérés comme de mauvaise qualité). Lorsque vous écrivez un serveur de longue durée (tel qu'un serveur web ou la gestion de base de données) système, qui ne sortent jamais), fuité la mémoire est un problème beaucoup plus important, et va éventuellement conduire à un crash lorsque l'application est à court de mémoire. Et bien sûr, la fuite de la mémoire est un problème encore plus important à l'intérieur un programme particulier: le système d'exploitation lui-même. Nous montrant une fois nouveau: ceux qui écrivent le code du noyau ont le plus difficile de tous...
à partir de la Page 7 de la Mémoire de l'API , chapitre 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)
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 fonctionne pendant des semaines à la fois et une boucle à l'intérieur du programme oublie de libérer quelques octets dans chaque itération, vous aurez une fuite de mémoire puissante qui dévorera toute la mémoire disponible dans votre ordinateur à moins que vous redémarrez sur une base régulière => même de petites fuites de mémoire pourrait être mauvais si le programme est utilisé pour une tâche très grande même si elle n'a pas été conçue à l'origine pour un.
je pense que vos deux exemples ne sont en fait qu'un: le free()
ne doit se produire qu'à la fin du processus, ce qui comme vous le faites remarquer est inutile puisque le processus est terminé.
dans votre deuxième exemple, la seule différence est que vous autorisez un nombre non défini de malloc()
, ce qui pourrait conduire à une perte de mémoire. La seule façon de gérer la situation est de vérifier le code de retour de malloc()
et d'agir en conséquence.