web-dev-qa-db-fra.com

Si j'ai besoin d'utiliser un morceau de mémoire pendant toute la durée de vie de mon programme, est-il vraiment nécessaire de le libérer juste avant la fin du programme?

Dans de nombreux livres et tutoriels, j'ai entendu la pratique de la gestion de la mémoire stressée et j'ai senti que des choses mystérieuses et terribles se produiraient si je ne libérais pas de mémoire après avoir fini de l'utiliser.

Je ne peux pas parler pour d'autres systèmes (même si pour moi, il est raisonnable de supposer qu'ils adoptent une pratique similaire), mais au moins sous Windows, le noyau est essentiellement garanti pour nettoyer la plupart des ressources (à l'exception d'un petit nombre) utilisées par un programme après la fin du programme. Qui comprend la mémoire de tas, entre autres choses.

Je comprends pourquoi vous voudriez fermer un fichier après l'avoir utilisé afin de le mettre à la disposition de l'utilisateur ou pourquoi vous voudriez déconnecter un socket connecté à un serveur afin d'économiser de la bande passante, mais il semble idiot de devez microgérer TOUTE votre mémoire utilisée par votre programme.

Maintenant, je suis d'accord que cette question est large car la façon dont vous devez gérer votre mémoire est basée sur la quantité de mémoire dont vous avez besoin et quand vous en avez besoin, donc je vais restreindre la portée de cette question à ceci: Si je dois utiliser un morceau de mémoire pendant toute la durée de vie de mon programme, est-il vraiment nécessaire de le libérer juste avant la fin du programme?

Edit: La question suggérée comme doublon était spécifique à la famille de systèmes d'exploitation Unix. Sa première réponse spécifiait même un outil spécifique à Linux (par exemple Valgrind). Cette question est destinée à couvrir la plupart des systèmes d'exploitation non intégrés "normaux" et pourquoi il est ou non une bonne pratique de libérer de la mémoire qui est nécessaire tout au long de la durée de vie d'un programme.

63
CaptainObvious

Si j'ai besoin d'utiliser un morceau de mémoire pendant toute la durée de vie de mon programme, est-il vraiment nécessaire de le libérer juste avant la fin du programme?

Ce n'est pas obligatoire, mais cela peut avoir des avantages (ainsi que certains inconvénients).

Si le programme alloue de la mémoire une fois pendant son temps d'exécution et ne la libérerait jamais avant la fin du processus, il peut être judicieux de ne pas libérer la mémoire manuellement et de se fier au système d'exploitation. Sur tous les systèmes d'exploitation modernes que je connais, cela est sûr, à la fin du processus, toute la mémoire allouée est retournée de manière fiable au système.
Dans certains cas, le nettoyage explicite de la mémoire allouée peut même être beaucoup plus rapide que le nettoyage.

Cependant, en libérant explicitement toute la mémoire à la fin de l'exécution,

  • pendant le débogage/test, les outils de détection de fuite mem ne vous montreront pas de "faux positifs"
  • il peut être beaucoup plus facile de déplacer le code qui utilise la mémoire avec l'allocation et la désallocation dans un composant séparé et de l'utiliser plus tard dans un contexte différent où le temps d'utilisation de la mémoire doit être contrôlé par l'utilisateur du composant

La durée de vie des programmes peut changer. Votre programme est peut-être un petit utilitaire de ligne de commande aujourd'hui, avec une durée de vie typique de moins de 10 minutes, et il alloue de la mémoire en portions de quelques ko toutes les 10 secondes - donc pas besoin de libérer de la mémoire allouée avant la fin du programme. Plus tard, le programme est modifié et obtient une utilisation étendue dans le cadre d'un processus serveur avec une durée de vie de plusieurs semaines - donc ne pas libérer la mémoire inutilisée entre les deux n'est plus une option, sinon votre programme commence à consommer toute la mémoire du serveur disponible au fil du temps . Cela signifie que vous devrez revoir l'ensemble du programme et ajouter du code de désallocation par la suite. Si vous avez de la chance, c'est une tâche facile, sinon, cela peut être si difficile que les chances sont élevées que vous manquez une place. Et lorsque vous êtes dans cette situation, vous souhaiterez avoir ajouté le code "gratuit" à votre programme au moment où vous avez ajouté le code "malloc".

Plus généralement, l'écriture de code d'allocation et de désallocation connexe toujours par paire compte comme une "bonne habitude" chez de nombreux programmeurs: en faisant cela toujours, vous diminuez la probabilité d'oublier le code de désallocation dans les situations où la mémoire doit être libéré.

106
Doc Brown

Libérer de la mémoire à la fin d'un programme n'est qu'une perte de temps CPU. C'est comme ranger une maison avant de la faire sortir de l'orbite.

Cependant, parfois, ce qui était un programme court peut se transformer en partie beaucoup plus longue. Ensuite, libérer des trucs devient nécessaire. Si cela n'a pas été pensé au moins dans une certaine mesure, cela peut impliquer un remaniement substantiel.

Une solution intelligente à cela est "talloc" qui vous permet d'effectuer une charge d'allocations de mémoire, puis de les jeter toutes en un seul appel.

11
Peter Green

Vous pouvez utiliser un langage avec garbage collection (comme Scheme, Ocaml, Haskell, Common LISP et même Java, Scala, Clojure).

(Dans la plupart des langues éditées par GC, il y a pas question à explicitement et manuellement mémoire libre! Parfois, certaines valeurs peuvent être finalisées , par exemple le GC et le système d'exécution fermeraient une valeur de descripteur de fichier lorsque cette valeur est inaccessible; mais ce n'est pas sûr, peu fiable, et vous devriez plutôt fermer explicitement vos poignées de fichier, car la finalisation n'est jamais garantie)

Vous pouvez également, pour votre programme codé en C (ou même C++) utiliser le garbage collector conservateur de Boehm . Vous devez alors remplacer tous vos malloc par GC_malloc et ne vous souciez pas de free - pour tout pointeur. Bien sûr, vous devez comprendre les avantages et les inconvénients de l'utilisation du GC de Boehm. Lisez aussi le manuel GC .

La gestion de la mémoire est une propriété globale d'un programme. D'une certaine manière, elle (et la vivacité de certaines données données) est non compositionnelle et non modulaire, car toute une propriété de programme.

Enfin, comme d'autres l'ont souligné, explicitement free - la zone de mémoire C allouée en tas est une bonne pratique. Pour un programme de jouet n'allouant pas beaucoup de mémoire, vous pouvez même décider de ne pas du tout free mémoire (puisque lorsque le processus est terminé, ses ressources, y compris son espace d'adressage virtuel , serait libéré par le système d'exploitation).

Si j'ai besoin d'utiliser un morceau de mémoire pendant toute la durée de vie de mon programme, est-il vraiment nécessaire de le libérer juste avant la fin du programme?

Non, tu n'as pas besoin de faire ça. Et de nombreux programmes du monde réel ne prennent pas la peine de libérer de la mémoire qui est nécessaire sur toute la durée de vie (en particulier le compilateur GCC ne libère pas une partie de sa mémoire) . Cependant, lorsque vous faites cela (par exemple, vous ne dérangez pas free - ing un morceau particulier de C alloué dynamiquement données), vous ferez mieux commenter ce fait pour faciliter le travail des futurs programmeurs sur le même projet. J'ai tendance à recommander que la quantité de mémoire non libérée reste limitée, et généralement relativement petite par rapport au poids. à la mémoire de tas totale utilisée.

Notez que le système free ne libère souvent pas de mémoire sur le système d'exploitation (par exemple en appelant munmap (2) sur les systèmes POSIX) mais marque généralement une zone mémoire comme réutilisable par le futur malloc. En particulier, l'espace d'adressage virtuel (par exemple, comme vu à travers /proc/self/maps sous Linux, voir proc (5) ....) pourrait ne pas diminuer après free (d'où les utilitaires comme ps ou top sont rapportant la même quantité de mémoire utilisée pour votre processus).

5

Ce n'est pas nécessaire, car vous ne manquerez pas d'exécuter votre programme correctement si vous ne le faites pas. Cependant, il y a des raisons pour lesquelles vous pouvez choisir, si vous en avez l'occasion.

L'un des cas les plus puissants que je rencontre (encore et encore) est que quelqu'un écrit un petit morceau de code de simulation qui s'exécute dans son exécutable. Ils disent "nous aimerions que ce code soit intégré dans la simulation". Je leur demande ensuite comment ils prévoient de réinitialiser entre les courses de monte-carlo, et ils me regardent d'un air vide. "Que voulez-vous dire par réinitialiser? Vous venez d'exécuter le programme avec de nouveaux paramètres?"

Parfois, un nettoyage propre rend beaucoup plus facile l'utilisation de votre logiciel. Dans le cas de nombreux exemples, vous présumez que vous n'avez jamais besoin de nettoyer quelque chose et vous faites des hypothèses sur la façon dont vous pouvez gérer les données et leur durée de vie autour de ces présomptions. Lorsque vous passez à un nouvel environnement où ces présomptions ne sont pas valides, des algorithmes entiers peuvent ne plus fonctionner.

Pour voir à quel point des choses étranges peuvent arriver, regardez comment les langages gérés gèrent la finalisation à la fin des processus, ou comment C # gère l'arrêt programmatique des domaines d'application. Ils se nouent parce qu'il y a des hypothèses qui passent à travers les mailles du filet dans ces cas extrêmes.

3
Cort Ammon

Ignorer les langues où vous ne libérez pas la mémoire manuellement de toute façon ...

Ce que vous considérez comme un "programme" en ce moment pourrait à un moment donné devenir simplement une fonction ou une méthode qui fait partie d'un programme plus vaste. Et puis cette fonction pourrait être appelée plusieurs fois. Et puis la mémoire que vous auriez dû "libérer manuellement" sera une fuite de mémoire. C'est bien sûr un appel au jugement.

1
gnasher729

Non, ce n'est pas nécessaire, mais c'est une bonne idée.

Vous dites que vous pensez que "des choses mystérieuses et terribles se produiraient si je ne libérais pas de mémoire après avoir fini de l'utiliser."

En termes techniques, la seule conséquence de cela est que votre programme continuera à manger plus de mémoire jusqu'à ce qu'une limite stricte soit atteinte (par exemple, votre espace d'adressage virtuel est épuisé) ou que les performances deviennent inacceptables. Si le programme est sur le point de se terminer, rien de tout cela n'a d'importance car le processus cesse effectivement d'exister. Les "choses mystérieuses et terribles" concernent purement l'état mental du développeur. Trouver la source d'une fuite de mémoire peut être un cauchemar absolu (c'est un euphémisme) et il faut beaucoup de compétence et de discipline pour écrire du code sans fuite. L'approche recommandée pour développer cette compétence et cette discipline consiste à toujours libérer de la mémoire une fois qu'elle n'est plus nécessaire, même si le programme est sur le point de se terminer.

Bien sûr, cela a l'avantage supplémentaire que votre code peut être réutilisé et adapté plus facilement, comme d'autres l'ont dit.

Cependant, il y a au moins un cas où il vaut mieux pas libérer de la mémoire juste avant la fin du programme.

Considérez le cas où vous avez fait des millions de petites allocations, et elles ont surtout été échangées sur le disque. Lorsque vous commencez à tout libérer, la majeure partie de votre mémoire doit être remplacée par RAM pour que les informations de comptabilité soient accessibles, uniquement pour éliminer immédiatement les données. Cela peut faire quelques minutes juste pour sortir! Sans parler de mettre beaucoup de pression sur le disque et la mémoire physique pendant ce temps. Si la mémoire physique est insuffisante pour commencer (peut-être que le programme est en cours de fermeture car un autre programme mémoire), alors des pages individuelles peuvent devoir être échangées plusieurs fois lorsque plusieurs objets doivent être libérés de la même page, mais ne sont pas libérés consécutivement.

Si, à la place, le programme est simplement abandonné, le système d'exploitation supprimera simplement toute la mémoire qui a été échangée sur le disque, ce qui est presque instantané car il ne nécessite aucun accès au disque.

Il est important de noter que dans un langage OO, appeler le destructeur d'un objet forcera également l'échange de mémoire; si vous devez le faire, vous pouvez tout aussi bien libérer la mémoire.

1
Artelius

Parce qu'il est très probable que dans un certain temps, vous souhaitiez modifier votre programme, peut-être l'intégrer à autre chose, exécuter plusieurs instances en séquence ou en parallèle. Ensuite, il deviendra nécessaire de libérer manuellement cette mémoire - mais vous ne vous souviendrez plus des circonstances et cela vous coûtera beaucoup plus de temps pour comprendre à nouveau votre programme.

Faites des choses pendant que votre compréhension à leur sujet est encore fraîche.

C'est un petit investissement qui peut rapporter de gros bénéfices à l'avenir.

1
Agent_L

Les principales raisons de nettoyer manuellement sont les suivantes: vous risquez moins de ne pas confondre une véritable fuite de mémoire avec un bloc de mémoire qui sera simplement nettoyé à la sortie, vous n'attraperez parfois des bogues dans l'allocateur que lorsque vous parcourrez l'intégralité de la structure de données jusqu'à désallouez-le, et vous aurez un mal de tête beaucoup plus petit si vous refactorisez un jour afin que certains objets ne aient besoin d'être désalloués avant la fin du programme.

Les principales raisons de ne pas nettoyer manuellement sont: les performances, les performances, les performances et la possibilité d'une sorte de bogue gratuit ou double après utilisation dans votre code de nettoyage inutile, qui peut se transformer en bogue de plantage ou en sécurité exploit.

Si vous vous souciez des performances, vous voulez toujours profiler pour savoir où vous perdez tout votre temps, au lieu de deviner. Un compromis possible consiste à encapsuler votre code de nettoyage facultatif dans un bloc conditionnel, à le laisser lors du débogage afin de profiter des avantages de l'écriture et du débogage du code, puis, si et seulement si vous avez déterminé empiriquement que c'est trop surcharge, dites au compilateur de le sauter dans votre exécutable final. Et un retard dans la fermeture du programme est, presque par définition, presque jamais dans le chemin critique.

0
Davislor