Jusqu'où peuvent aller les fuites de mémoire?


118

J'ai rencontré plusieurs fois des fuites de mémoire. Habituellement, quand je suis malloccomme s'il n'y avait pas de lendemain, ou que je pendais comme du FILE *linge sale. Je suppose généralement (lire: espère désespérément) que toute la mémoire est nettoyée au moins lorsque le programme se termine. Y a-t-il des situations où la mémoire perdue ne sera pas collectée lorsque le programme se termine ou se bloque?

Si la réponse varie considérablement d'un langage à l'autre, concentrons-nous sur C (++).

Veuillez noter l'utilisation hyperbolique de la phrase «comme s'il n'y avait pas de lendemain» et «pendant ... comme du linge sale». Un danger * malloc* peut blesser ceux que vous aimez. Veuillez également faire preuve de prudence avec le linge sale.


3
Si vous utilisez un système d'exploitation «moderne» comme Linux ou Windows, le système d'exploitation lui-même résoudra toute mémoire non libérée lorsque le programme se terminera.
Oliver Charlesworth

60
Au lieu de faire du mal comme s'il n'y avait pas de lendemain, essayez de faire comme s'il y avait un demain et gardez une trace de votre mémoire!
William Pursell

8
@WilliamPursell ah, alors vous dites que l'on devrait callocaimer qu'il n'y ait pas de lendemain. Excellent.
DilithiumMatrix

8
"Si la réponse varie considérablement d'un langage à l'autre, concentrons-nous sur c (++)." c et c ++ ne sont pas le même langage!
Johnsyweb

11
@zhermes: Le commentaire sur le fait que C et C ++ sont des langages différents cache plus que vous ne le pensez ... En C ++, vous vous retrouverez plutôt à profiter des objets avec une durée de stockage automatique, suivez l'idiome RAII ... vous laissez ces objets s'occuper de la mémoire gestion pour vous.
LihO

Réponses:


111

Non. Les systèmes d'exploitation libèrent toutes les ressources détenues par les processus lorsqu'ils se terminent.

Cela s'applique à toutes les ressources gérées par le système d'exploitation: mémoire, fichiers ouverts, connexions réseau, poignées de fenêtre ...

Cela dit, si le programme s'exécute sur un système embarqué sans système d'exploitation, ou avec un système d'exploitation très simple ou bogué, la mémoire peut être inutilisable jusqu'au redémarrage. Mais si vous étiez dans cette situation, vous ne poseriez probablement pas cette question.

Le système d'exploitation peut mettre du temps à libérer certaines ressources. Par exemple, le port TCP utilisé par un serveur réseau pour accepter les connexions peut mettre quelques minutes à se libérer, même s'il est correctement fermé par le programme. Un programme en réseau peut également contenir des ressources distantes telles que des objets de base de données. Le système distant doit libérer ces ressources lorsque la connexion réseau est perdue, mais cela peut prendre encore plus de temps que le système d'exploitation local.


5
Un paradigme courant dans les RTOS est le modèle à processus unique, à plusieurs threads et aucune protection de mémoire entre les «tâches». Il y a généralement un tas. C'est certainement ainsi que VxWorks fonctionnait - et le fait probablement encore.
marko

29
Notez que toutes les ressources ne peuvent pas être libérées par le système d'exploitation. Les connexions réseau, les transactions de base de données, etc., ne pas les fermer explicitement peuvent entraîner des résultats indésirables. Le fait de ne pas fermer la connexion réseau peut amener le serveur à penser que vous êtes toujours actif pendant une période indéterminée, et pour les serveurs qui limitent le nombre de connexions actives, peut accidentellement provoquer un déni de service. Ne pas fermer les transactions de base de données peut vous faire perdre des données non validées.
Lie Ryan

1
@Marko: la version récente de vxWorks prend désormais en charge les RTP (processus en temps réel) qui prennent en charge la protection de la mémoire.
Xavier T.

20
"Les systèmes d'exploitation libèrent toutes les ressources détenues par les processus lorsqu'ils se terminent." Pas strictement vrai. Par exemple, sur (au moins) Linux, les sémaphores SysV et autres objets IPC ne sont pas nettoyés à la sortie du processus. C'est pourquoi il existe ipcrmpour le nettoyage manuel, linux.die.net/man/8/ipcrm .
sleske

7
De plus, si un objet a un fichier temporaire qu'il conserve, il ne sera clairement pas nettoyé par la suite.
Mooing Duck

47

La norme C ne spécifie pas que la mémoire allouée par mallocest libérée lorsque le programme se termine. Ceci fait par le système d'exploitation et tous les systèmes d'exploitation (généralement ceux-ci sont dans le monde embarqué) libèrent la mémoire lorsque le programme se termine.


20
C'est plus ou moins parce que la norme C parle des programmes C, pas des systèmes d'exploitation sur lesquels C se trouve à s'exécuter ...
vonbrand

5
@vonbrand Le standard C aurait pu avoir un paragraphe qui dit que lorsque les mainretours, toute la mémoire allouée par mallocest libérée. Par exemple, il indique que tous les fichiers ouverts sont fermés avant la fin du programme. Pour la mémoire allouée my malloc, il n'est tout simplement pas spécifié. Maintenant, bien sûr, ma phrase concernant le système d'exploitation décrit ce qui est généralement fait et non ce que la norme prescrit, car elle ne spécifie rien à ce sujet.
ouah

Permettez-moi de corriger mon commentaire: la norme parle de C, pas de la façon dont le programme est démarré et arrêté. Vous pouvez très bien écrire un programme C qui fonctionne sans OS. Dans ce cas, personne ne fera le nettoyage. Le standard ne spécifie rien de manière très délibérée, sauf en cas de besoin, afin de ne pas contraindre les utilisations sans nécessité.
vonbrand

2
@ouah: " quand le retour principal ...". C'est une hypothèse. Nous devons considérer " si les retours principaux ...". std::atexitconsidère également la terminaison du programme via std::exit, et puis il y a aussi std::abortet (spécifique à C ++) std::terminate.
MSalters

@ouah: Si cela avait été inclus, atexitne serait pas utilisable. :-)
R .. GitHub STOP AIDER ICE

28

Comme toutes les réponses ont couvert la plupart des aspects de votre question par rapport aux systèmes d'exploitation modernes, mais historiquement, il y en a une qui mérite d'être mentionnée si vous avez déjà programmé dans le monde DOS. Les programmes Terminant et Stay Resident (TSR) retournaient généralement le contrôle du système mais résideraient dans la mémoire qui pourrait être réactivée par une interruption logicielle / matérielle. Il était normal de voir des messages comme "mémoire insuffisante! Essayez de décharger certains de vos TSR" lorsque vous travaillez sur ces systèmes d'exploitation.

Donc, techniquement, le programme se termine , mais comme il réside toujours en mémoire, aucune fuite de mémoire ne sera libérée à moins que vous ne déchargiez le programme.

Vous pouvez donc considérer cela comme un autre cas en dehors des systèmes d'exploitation qui ne récupèrent pas de mémoire, soit parce que c'est bogué, soit parce que le système d'exploitation intégré est conçu pour le faire.

Je me souviens d'un autre exemple. Le système de contrôle des informations client (CICS), un serveur de transactions qui s'exécute principalement sur des mainframes IBM est pseudo-conversationnel. Lorsqu'il est exécuté, il traite les données saisies par l'utilisateur, génère un autre ensemble de données pour l'utilisateur, le transfère au nœud de terminal utilisateur et se termine. Lors de l'activation de la touche d'attention, il revient à nouveau pour traiter un autre ensemble de données. En raison de la façon dont il se comporte, techniquement encore, le système d'exploitation ne récupérera pas la mémoire des programmes CICS terminés, à moins que vous ne recycliez le serveur de transactions CICS.


C'est vraiment intéressant, merci pour la note historique! Savez-vous si ce paradigme était dû au fait que la libération de mémoire était trop coûteuse en calcul si ce n'était pas nécessaire? Ou est-ce que l'alternative n'avait tout simplement jamais été envisagée?
DilithiumMatrix

1
@zhermes: C'était impossible sur le plan informatique, car DOS ne suivait tout simplement pas les allocations de mémoire pour les TSR. À peu près par définition: le but était de rester résident . Si vous vouliez que votre TSR libère une partie de la mémoire mais pas toute la mémoire, c'était à vous de décider quoi libérer.
MSalters

2
@zhermes: DOS (comme CP / M, son ancêtre) n'était pas ce que vous appelleriez un système d'exploitation au sens moderne du terme. C'était en fait juste une collection d'utilitaires d'E / S qui pouvaient être appelés de manière standard avec un processeur de commandes qui vous permettrait d'exécuter un programme à la fois. Il n'y avait aucune notion de processus et la mémoire n'était ni virtuelle ni protégée. Les TSR étaient un hack utile qui pouvait dire au système qu'ils occupaient jusqu'à 64K d'espace et se connecteraient eux-mêmes aux interruptions afin qu'ils soient appelés.
Blrfl

8

Comme les autres l'ont dit, la plupart des systèmes d'exploitation récupéreront la mémoire allouée à la fin du processus (et probablement d'autres ressources telles que les sockets réseau, les descripteurs de fichiers, etc.).

Cela dit, la mémoire n'est peut-être pas la seule chose dont vous devez vous soucier lorsque vous traitez avec new / delete (au lieu de raw malloc / free). La mémoire allouée dans new peut être récupérée, mais les choses qui peuvent être faites dans les destructeurs des objets ne se produiront pas. Peut-être que le destructeur d'une classe écrit une valeur sentinelle dans un fichier lors de la destruction. Si le processus se termine, le descripteur de fichier peut être vidé et la mémoire récupérée, mais cette valeur sentinelle ne sera pas écrite.

Morale de l'histoire, nettoyez toujours après vous-même. Ne laissez pas les choses pendre. Ne comptez pas sur le nettoyage du système d'exploitation après vous. Nettoyez après vous.


Ne comptez pas sur le nettoyage du système d'exploitation après vous. Nettoyez après vous. C'est souvent imp ... 'très, très difficile' avec des applications multithread complexes. Les fuites réelles, où toutes les références à une ressource ont été perdues, sont mauvaises. Autoriser le système d'exploitation à nettoyer au lieu de libérer explicitement des références n'est pas toujours une mauvaise chose et souvent la seule voie raisonnable à prendre.
Martin James

1
En C ++, les destructeurs seront appelés à la fin du programme (à moins qu'un kill -9fan moins brillant ne se présente ...)
vonbrand

@vonbrand True, mais si nous parlons de fuites avec des objets dynamiques, ces destructeurs ne se produiront pas. L'objet hors de portée est un pointeur brut et son destructeur est un no-op. (Bien sûr, voir les objets RAII pour atténuer ce problème ...)
Andre Kostur

1
Le problème avec RAII est qu'il insiste pour désallouer des objets à la sortie du processus dont il n'est pas vraiment important de se débarrasser. Les connexions de base de données auxquelles vous voulez faire attention, mais la mémoire générale est mieux nettoyée par le système d'exploitation (il fait un bien meilleur travail). Le problème se manifeste comme un programme qui prend un temps fou à se terminer une fois que la quantité de mémoire paginée augmente. C'est également non trivial à résoudre…
Donal Fellows

@vonbrand: Ce n'est pas si simple. std::exitappellera des dtors, std::abortpas, des exceptions non interceptées pourraient.
MSalters

7

Cela dépendra plus probablement du système d'exploitation que de la langue. En fin de compte, tout programme dans n'importe quelle langue obtiendra sa mémoire du système d'exploitation.

Je n'ai jamais entendu parler d'un système d'exploitation qui ne recycle pas la mémoire lorsqu'un programme se ferme / se bloque. Donc, si votre programme a une limite supérieure sur la mémoire qu'il doit allouer, alors simplement allouer et ne jamais libérer est parfaitement raisonnable.


Pourriez-vous bousiller l'image mémoire du noyau dans le cas d'un OS simpliste? .. Comme, ces systèmes d'exploitation sans même multitâche.
ulidtko

@ulidtko, cela va tout gâcher . Si mon programme nécessite, disons 1 Go de temps en temps, et le saisit pour la durée, il refuse l'utilisation de ces ressources à d'autres, même sans l'utiliser. Cela peut avoir de l'importance aujourd'hui, ou pas. Mais l'environnement va changer radicalement. Garanti.
vonbrand

@vonbrand L'utilisation rare de 1GiB n'est normalement pas un problème (tant que vous avez beaucoup de mémoire physique) car les systèmes d'exploitation modernes peuvent supprimer les bits qui ne sont pas actuellement actifs. Le problème survient lorsque vous avez plus de mémoire virtuelle en utilisation active que de mémoire physique dans laquelle l'héberger.
Donal Fellows

5

Si le programme est un jour transformé en un composant dynamique ("plugin") qui est chargé dans l'espace d'adressage d'un autre programme, cela sera gênant, même sur un système d'exploitation avec une gestion de mémoire ordonnée. Nous n'avons même pas à penser au code porté sur des systèmes moins performants.

D'un autre côté, la libération de toute la mémoire peut avoir un impact sur les performances du nettoyage d'un programme.

Un programme sur lequel je travaillais, un certain cas de test a nécessité 30 secondes ou plus pour que le programme se termine, car il revenait à travers le graphique de toute la mémoire dynamique et le libérait morceau par morceau.

Une solution raisonnable consiste à avoir la capacité là-bas et à la couvrir de cas de test, mais à la désactiver dans le code de production afin que l'application se ferme rapidement.


5

Tous les systèmes d'exploitation méritant le titre nettoieront le désordre causé par votre processus après la résiliation. Mais il y a toujours des événements imprévus, que se passe-t-il s'il s'est vu refuser l'accès d'une manière ou d'une autre et qu'un pauvre programmeur n'a pas prévu la possibilité et qu'il ne réessaye pas un peu plus tard? Toujours plus sûr de simplement nettoyer vous-même SI les fuites de mémoire sont essentielles à la mission - sinon, cela ne vaut pas vraiment la peine OMI si cet effort est coûteux.

Edit: Vous devez nettoyer les fuites de mémoire si elles sont en place où elles vont s'accumuler, comme dans les boucles. Les fuites de mémoire dont je parle sont celles qui s'accumulent en temps constant tout au long du programme, si vous avez une fuite de toute autre sorte, ce sera probablement un problème sérieux tôt ou tard.

En termes techniques si vos fuites sont de «complexité» mémoire O (1) elles sont correctes dans la plupart des cas, O (logn) déjà désagréable (et dans certains cas fatal) et O (N) + intolérable.


3

La mémoire partagée sur les systèmes compatibles POSIX persiste jusqu'à ce que shm_unlink soit appelé ou que le système soit redémarré.


2

Si vous avez une communication interprocessus, cela peut amener d'autres processus à ne jamais terminer et consommer des ressources en fonction du protocole.

Pour donner un exemple, j'ai déjà expérimenté l'impression sur une imprimante PDF en Java lorsque j'ai arrêté la JVM au milieu d'un travail d'impression, le processus de spoule PDF est resté actif et j'ai dû le tuer dans le gestionnaire de tâches avant de pouvoir relancez l'impression.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.