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 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 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 = 123
et 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 malloc
car 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?
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.Réponses:
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.
la source
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!)
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.
la source
=== 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".
la source
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 ellefree
n'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.null
s'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 surnull
quand 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.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!
la source
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:
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.
la source
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 êtrefoo_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'à:
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:
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» :)
la source
free() is cheap
sauf 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.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.
la source
free
àexit
temps considéré comme néfaste.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.la source
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.
la source
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.
la source
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:
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.
la source
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.
la source
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.
la source
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.
la source
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.
la source
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.
la source
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 demalloc()
et d'agir en conséquence.la source