Gestion manuelle de la mémoire - Manual memory management

En informatique , la gestion manuelle de la mémoire fait référence à l'utilisation d'instructions manuelles par le programmeur pour identifier et désallouer les objets inutilisés, ou les ordures . Jusqu'au milieu des années 1990, la majorité des langages de programmation utilisés dans l'industrie prenaient en charge la gestion manuelle de la mémoire, bien que le ramasse-miettes existe depuis 1959, date de son introduction avec Lisp . Aujourd'hui, cependant, les langages avec ramasse-miettes tels que Java sont de plus en plus populaires et les langages Objective-C et Swift offrent des fonctionnalités similaires via Automatic Reference Counting . Les principaux langages à gestion manuelle encore largement utilisés aujourd'hui sont le C et le C++ – voir allocation dynamique de mémoire C .

La description

Tous les langages de programmation utilisent des techniques manuelles pour déterminer quand allouer un nouvel objet à partir du magasin gratuit. C utilise la mallocfonction ; C++ et Java utilisent l' newopérateur ; et de nombreux autres langages (tels que Python) allouent tous les objets du magasin gratuit. Déterminer quand un objet doit être créé ( création d'objets ) est généralement trivial et sans problème, bien que des techniques telles que les pools d'objets signifient qu'un objet peut être créé avant une utilisation immédiate. Le vrai défi est la destruction d'objets - déterminer quand un objet n'est plus nécessaire (c'est-à-dire qu'il s'agit de déchets) et s'arranger pour que son stockage sous-jacent soit renvoyé au magasin gratuit pour être réutilisé. Dans l'allocation de mémoire manuelle, ceci est également spécifié manuellement par le programmeur ; via des fonctions comme free()en C, ou l' deleteopérateur en C++ – cela contraste avec la destruction automatique des objets contenus dans les variables automatiques , notamment les variables locales (non statiques) des fonctions, qui sont détruites à la fin de leur portée en C et C++.

Techniques de gestion manuelle de la mémoire

Par exemple


Gestion manuelle et correction

La gestion manuelle de la mémoire est connue pour activer plusieurs classes majeures de bogues dans un programme lorsqu'il est utilisé de manière incorrecte, notamment des violations de la sécurité de la mémoire ou des fuites de mémoire . Il s'agit d'une source importante de bogues de sécurité .

  • Lorsqu'un objet inutilisé n'est jamais remis dans le magasin libre, cela s'appelle une fuite de mémoire . Dans certains cas, les fuites de mémoire peuvent être tolérables, comme un programme qui "fuit" une quantité limitée de mémoire au cours de sa durée de vie, ou un programme de courte durée qui s'appuie sur un système d'exploitation pour désallouer ses ressources lorsqu'il se termine. Cependant, dans de nombreux cas, des fuites de mémoire se produisent dans des programmes de longue durée et, dans ce cas, une quantité illimitée de mémoire est divulguée. Lorsque cela se produit, la taille du magasin gratuit disponible continue de diminuer au fil du temps ; lorsqu'il est enfin épuisé, le programme se bloque alors.
  • Une défaillance catastrophique du système de gestion de mémoire dynamique peut se produire lorsque la mémoire de sauvegarde d'un objet est supprimée plus d'une fois ; un objet est explicitement détruit plus d'une fois ; lorsque, tout en utilisant un pointeur pour manipuler un objet non alloué sur la mémoire libre, un programmeur tente de libérer la mémoire de sauvegarde de l'objet cible dudit pointeur ; ou lorsque, tout en manipulant un objet via un pointeur vers une autre zone de mémoire arbitraire gérée par une tâche, un thread ou un processus externe inconnu, un programmeur corrompt l'état de cet objet, éventuellement de manière à écrire en dehors de ses limites et à corrompre ses données de gestion de mémoire. Le résultat de telles actions peut inclure une corruption de tas , une destruction prématurée d'un objet différent (et nouvellement créé) qui occupe le même emplacement en mémoire que l'objet supprimé plusieurs fois, des plantages du programme en raison d'un défaut de segmentation (violation de la protection de la mémoire ,) et d'autres formes de comportement indéfini .
  • Les pointeurs vers les objets supprimés deviennent des pointeurs sauvages s'ils sont utilisés après la suppression ; tenter d'utiliser de tels pointeurs peut entraîner des bogues difficiles à diagnostiquer.

Les langages qui utilisent exclusivement le ramasse-miettes sont connus pour éviter les deux dernières classes de défauts. Des fuites de mémoire peuvent toujours se produire (et des fuites limitées se produisent fréquemment avec le ramasse-miettes générationnel ou conservateur), mais sont généralement moins graves que les fuites de mémoire dans les systèmes manuels.

L'acquisition de ressources est l'initialisation

La gestion manuelle de la mémoire présente un avantage d'exactitude, à savoir qu'elle permet une gestion automatique des ressources via le paradigme RAII ( Resource Acquisition Is Initialization ).

Cela se produit lorsque les objets possèdent des ressources système limitées (comme des ressources graphiques, des descripteurs de fichiers ou des connexions de base de données) qui doivent être abandonnées lorsqu'un objet est détruit - lorsque la durée de vie de la propriété de la ressource doit être liée à la durée de vie de l'objet. Les langages à gestion manuelle peuvent arranger cela en acquérant la ressource lors de l'initialisation de l'objet (dans le constructeur), et en la libérant lors de la destruction de l'objet (dans le destructeur ), qui se produit à un moment précis. Ceci est connu sous le nom d'acquisition de ressources est l'initialisation.

Ceci peut également être utilisé avec un comptage de références déterministe . En C++, cette capacité est mise à profit pour automatiser la désallocation de mémoire dans un cadre autrement manuel, l'utilisation du shared_ptrmodèle dans la bibliothèque standard du langage pour effectuer la gestion de la mémoire est un paradigme courant. shared_ptrn'est cependant pas adapté à tous les modèles d'utilisation des objets.

Cette approche n'est pas utilisable dans la plupart des langages de récupération de mémoire - notamment le traçage des récupérateurs de mémoire ou le comptage de références plus avancé - car la finalisation n'est pas déterministe, et parfois ne se produit pas du tout. C'est-à-dire qu'il est difficile de définir (ou de déterminer) quand ou si une méthode de finalisation peut être appelée ; c'est ce qu'on appelle communément le problème du finaliseur . Java et d'autres langages GC'd utilisent fréquemment la gestion manuelle des ressources système rares en plus de la mémoire via le modèle dispose : tout objet qui gère les ressources est censé implémenter la dispose()méthode, qui libère ces ressources et marque l'objet comme inactif. Les programmeurs sont censés invoquer dispose()manuellement le cas échéant pour éviter les « fuites » de ressources graphiques rares. Selon la finalize()méthode (comment Java implémente les finaliseurs), la libération des ressources graphiques est largement considérée comme une mauvaise pratique de programmation par les programmeurs Java, et de même, la __del__()méthode analogue de Python ne peut pas être utilisée pour libérer des ressources. Pour les ressources de la pile (ressources acquises et publiées dans un seul bloc de code), cela peut être automatisé par diverses constructions de langage, telles que Python with, C# usingou Java try-with-resources.

Performance

De nombreux partisans de la gestion manuelle de la mémoire soutiennent qu'elle offre des performances supérieures par rapport aux techniques automatiques telles que le ramasse-miettes . Traditionnellement, la latence était le plus gros avantage, mais ce n'est plus le cas. L'allocation manuelle a souvent une localité de référence supérieure .

L'allocation manuelle est également connue pour être plus appropriée pour les systèmes où la mémoire est une ressource rare, en raison d'une récupération plus rapide. Les systèmes de mémoire peuvent et font fréquemment des « poussées » lorsque la taille de l' ensemble de travail d'un programme approche de la taille de la mémoire disponible ; les objets inutilisés dans un système de récupération de place restent dans un état non récupéré plus longtemps que dans les systèmes gérés manuellement, car ils ne sont pas immédiatement récupérés, ce qui augmente la taille effective de l'ensemble de travail.

La gestion manuelle présente un certain nombre d' inconvénients de performances documentés :

  • Les appels à deleteet autres engendrent une surcharge à chaque fois qu'ils sont effectués, cette surcharge peut être amortie dans les cycles de récupération de place. Cela est particulièrement vrai pour les applications multithread, où les appels de suppression doivent être synchronisés.
  • La routine d'allocation peut être plus compliquée et plus lente. Certains schémas de récupération de place, tels que ceux avec compactage de tas , peuvent conserver la mémoire libre sous la forme d'un simple tableau de mémoire (par opposition aux implémentations compliquées requises par les schémas de gestion manuelle).

La latence est un point débattu qui a changé au fil du temps, avec les premiers ramasse-miettes et les implémentations simples qui fonctionnent très mal par rapport à la gestion manuelle de la mémoire, mais les ramasse-miettes modernes et sophistiqués fonctionnent souvent aussi bien ou mieux que la gestion manuelle de la mémoire.

L'allocation manuelle ne souffre pas des longs temps de "pause" qui se produisent dans un simple ramasse-miettes stop-the-world, bien que les ramasse-miettes modernes aient des cycles de collecte qui ne sont souvent pas perceptibles.

La gestion manuelle de la mémoire et le garbage collection souffrent tous deux de temps de désallocation potentiellement illimités - la gestion manuelle de la mémoire car la désallocation d'un seul objet peut nécessiter la désallocation de ses membres, et récursivement les membres de ses membres, etc., tandis que le garbage collection peut avoir de longs cycles de collecte. C'est particulièrement un problème dans les systèmes en temps réel, où les cycles de collecte illimités sont généralement inacceptables ; le ramasse-miettes en temps réel est possible en mettant le ramasse-miettes en pause, tandis que la gestion manuelle de la mémoire en temps réel nécessite d'éviter de grandes désallocations, ou de suspendre manuellement la désallocation.

Les références

  • Berger, éd ; Zorn, BG ; McKinley, KS (novembre 2002). "Reconsidérer l'allocation de mémoire personnalisée". Actes de la 17e conférence ACM SIGPLAN sur la programmation, les systèmes, les langages et les applications orientés objet . OOPSLA '02. p. 1-12. CiteSeerX  10.1.1.119.5298 . doi : 10.1145/582419.582421 . ISBN 1-58113-471-1.

Liens externes