Gestion de mémoire implicite

Gestion de mémoire implicite

Gestion de la mémoire : un état de l’art

Gestion du cycle de vie des objets

Introduction

L’exécution d’algorithmes d’une application informatique consomme essentiellement deux ressources : le temps et l’espace mémoire. En machine, l’espace en question, où toute création et gestion d’objets, de variables et de structures de données est effectuée, peut être la mémoire vive ou la mémoire de masse persistante. Les composantes d’une application (objets, variables, structures de données) pourront être modifiées lors de son déroulement et elles seront détruites une fois cette même application achevée. Ces étapes de création, de l’utilisation et de destruction d’objets forment le cycle de vie d’un objet [Bray, 1977]. Lors de l’appel d’un objet, celui-ci est initialise en mémoire centrale et occupera un espace mémoire qui correspondra à sa taille. Une fois l’application finie, ce même objet, devenu inutile, devra être détruit afin que l’espace mémoire utilisé soit récupéré. On parle tout simplement de gestion de la mémoire. De cette manière, on fait en sorte que des objets ou des variables, qui ne sont plus d’aucune utilité, n’occupent pas un espace mémoire qui pourrait être utile pour d’autres objets ou variables [Metropolis et al, 1980]. Le principe même de cycle de vie d’une variable ou d’un objet est fondé sur le fait que toute création finit par une destruction, sinon le cycle serait incomplet [Bray, 1977]. Le fait qu’un objet ne soit plus sous le contrôle d’une application et qu’il ne soit pas encore libéré représente un gaspillage au niveau de l’espace mémoire. Cette forme de gaspillage, appelée fuite de mémoire [Hirzel et al, 2002], est une
erreur que la plupart de langages de programmation, voire la totalité, ont essayé d’en venir à bout. Dans le domaine de la gestion de la mémoire, nous distinguons deux tâches connexes: une étape d’allocation de la mémoire (création) et une autre de désallocation (destruction ou libération). La première se résume au fait d’allouer ou de réserver de la mémoire pour des objets, des variables ou encore des structures de données, et la seconde s’occupe de libérer les espaces mémoire déjà alloués, mais non utilisés. Différentes techniques, autant pour l’allocation que pour la désallocation, ont été élaborées [Salagnac, 2004]. On parlait au début des années cinquante d’une gestion statique de la mémoire, pour ensuite passer à une gestion dynamique vers le début des années soixante [Metropolis et al, 1980].
Le fait d’allouer statiquement de la mémoire pour un programme donné signifie avoir prévu l’espace mémoire nécessaire avant même l’exécution du programme, et ceci en spécifiant la quantité de mémoire nécessaire à partir du code source général. Lors de la compilation, cet espace mémoire sera réservé dans un fichier binaire. Au chargement du programme en mémoire (juste avant l’exécution) l’espace réservé devient donc accessible. Vu que l’allocation de la mémoire s’effectue avant l’exécution de l’application, un gain au niveau des performances est atteint (les coûts de l’allocation dynamique à l’exécution sont épargnés). La mémoire statique est immédiatement utilisable. Une gestion de mémoire statique peut être vue comme la gestion la plus sécuritaire dans le sens où la quantité de mémoire consommée est constante et complètement connue avant l’exécution. Pour les programmeurs dont les besoins peuvent varier de façon imprévisible, ce genre de gestion est très inflexible et insuffisant [Metropolis et al, 1980]. Prenons l’exemple d’une application informatique qui a besoin de 50 entiers comme entrée initiale. Le programmeur alloue un tableau statique pouvant contenir 100 entiers, il prévoit le cas où l’application aurait à augmenter le nombre de ces entrées. Si un jour l’application prend de l’ampleur et nécessitera un nombre d’entrées beaucoup plus grand, une redéfinition des espaces alloués serait nécessaire ; Le cas où cette situation se retrouve partout dans un code source constitue une grave problématique. Par conséquent, il est important d’avoir une gestion dynamique de la mémoire pour éviter une redéfinition obligatoire des espaces mémoire d’un programme (limite de gestion statique de la mémoire).
Contrairement à l’allocation qui ne peut se faire qu’explicitement, la libération quant à elle peut se faire de deux manières : soit explicitement (manuelle), soit implicitement (automatique). Dans le cas d’une désallocation explicite, le programmeur se retrouve à avoir un contrôle direct sur la libération de la mémoire. Aucun espace mémoire ne pourra être libéré sans des instructions venant directement du programmeur. La plupart des langages de programmation offrent des fonctions afin de permettre cette désallocation explicite. On peut citer les fonctions delete et free dans le cas du langage de programmation C++. Un principal avantage de ce genre de désallocation est qu’il est plus facile au programmeur de comprendre exactement ce qui se passe. Mais cela peut nécessiter une quantité importante de code source qui fera partie significativement de n’importe quelle interface de module. Ceci rend les bogues de gestion de mémoire plus fréquents. Plusieurs langages utilisent cette technique tels que C-C++, ADA.
En revanche, dans le cas d’une désallocation implicite, l’espace mémoire non utilisé est automatiquement récupéré. Ce type de gestion de mémoire est un service qui est considéré comme une partie ou une extension du langage de programmation. Les entités qui assurent la gestion de mémoire implicite, connues sous le nom de Garbage Collector, réalisent habituellement leurs tâches en libérant les blocs de mémoire non accessibles à partir des variables du programme (par exemple les variables qui ne peuvent être atteintes à partir des pointeurs) [Sioud, 2006]. Plusieurs langages utilisent cette technique, parmi lesquels on cite Java, Lisp, Eiffel, Modula III, etc.
Dans la section suivante, la gestion implicite via le Garbage Collector et ses limites seront décrites. L’ensemble d’erreurs engendrées suite à une mauvaise gestion de mémoire sera détaillé. Et enfin, les débogueurs de mémoire seront introduits comme la solution à ces problèmes et leurs rôles au niveau de la gestion de la mémoire seront expliqués.

Gestion implicite de la mémoire

Sûreté et sécurité sont des termes que des langages de programmation orientés objet comme Java, Lisps, Eiffel et Modula III ont utilisé pour gagner leur popularité. En effet, Meyer [1988] a placé la gestion automatique de la mémoire en troisième position dans les « sept commandements » de la programmation orientée objet. Pour Meyer [1988], une gestion dynamique de la mémoire via une désallocation automatique est une méthode clé pour garantir une bonne performance des langages de programmation. À cet effet, une entité appelée Garbage Collector ou ramasse-miette est utilisée pour assurer une gestion automatique de la mémoire. Un Garbage Collector est un sous-système informatique responsable du recyclage de la mémoire préalablement allouée puis inutilisée. Le premier à avoir utilisé un Garbage Collector fut John McCarthy en 1959 afin de remédier aux problèmes dus à une gestion manuelle de la mémoire au sein du premier système Lisp [Sioud, 2006]. Le principe de base de la récupération automatique de la mémoire est simple : une fois les objets qui ne sont plus utilisés sont détectés (c’est-à-dire les objets qui ne sont pas référencés et que le programme en cours d’exécution ne pourra jamais atteindre), on récupère l’espace mémoire que ces derniers ont utilisé [Jones et Lins, 1996] [Abdullahi et Ringwood,
1998].
II est possible de détecter à l’avance le moment où un objet ne sera plus utilisé, il est possible de le découvrir lors de l’exécution : un objet sur lequel un programme ne maintient plus de référence (objet devenu inaccessible) ne sera plus utilisé [Sioud, 2006]. Ainsi, un Garbage Collector construit le graphe d’objets atteignables qui est formé de tous les objets accessibles à partir de toute racine. Ces derniers ne seront plus utilisables et devraient rester en mémoire vu que leurs valeurs sont accessibles depuis les racines. Tous les objets qui ne seront pas accessibles par le programme seront alors collectés par le Garbage Collector puisqu’ils sont considérés comme des miettes. Dans l’exemple présenté à la Figure 2-1, E et F ne sont pas accessibles ni directement, ni par l’intermédiaire d’autres objets et ils sont considérés comme des miettes. Pile Registre Racine Mémoire.
Toutes les techniques de Garbage Collection supposent que les miettes doivent rester stables en mémoire et qu’elles ne peuvent en aucun cas redevenir vivantes. Un langage muni d’un Garbage Collector permet d’écrire des applications informatiques plus simples et plus sûres. Le fait que la mémoire soit gérée automatiquement par l’environnement d’exécution, le programmeur se libère de cette tâche. La gestion manuelle de mémoire est l’une des sources les plus courantes d’erreurs dont les trois principaux types sont i) l’accès à une zone mémoire non allouée ou qui a été libérée, ii) la libération d’une zone mémoire déjà libérée et ii) la non-libération de la mémoire inutilisée {fuites de mémoires).
L’utilisation d’outils et de méthodologies appropriés permet d’en réduire l’impact, tandis que l’utilisation d’un Garbage Collector permet de les éliminer presque complètement. En effet, les Garbage Collector n’arrivent pas à résoudre tous les problèmes de gestion de mémoires et ceci en tenant compte des problèmes d’allocations et de désallocation (par exemple les erreurs de dépassement de tampon buffer overflow). En effet, la fonction d’un Garbage Collector ne se limite pas à la libération seulement, mais aussi par son biais se fait l’opération d’allocation à travers une entité appelée mutateur [Abdullahi et Ringwood, 1998]. Cette simplification de tâche de gestion de mémoire à travers les Garbage Collector ne peut qu’apporter de l’aide aux programmeurs, mais peut aussi présenter quelques inconvénients, principalement au niveau des performances des programmes les utilisant. Des études montrent que dans certains cas l’implémentation d’un Garbage Collector augmente les performances d’un programme, dans d’autres cas le contraire se produit. Le choix de l’algorithme et des paramètres d’un Garbage Collector est important, car c’est de cette base qu’on peut altérer ou améliorer significativement les performances d’un programme ou encore le langage de programmation en question [Levanoni et Petrank, 2001].
Pour remédier aux failles citées ci-dessus, les spécialistes dans le domaine ont opté pour le développement d’un outil de programmation spécialisé dans la gestion de mémoire, que se soit au niveau de l’allocation ou encore de la désallocation : i) un outil qui serait capable de détecter les fuites de mémoires (memory leaks) et les erreurs de dépassement de tampon (buffer overflow), et ii) un outil qui est capable d’éviter les erreurs d’allocations et de fournir ce qu’on appelle une allocation sécurisée. En effet, c’est là qu’intervient l’outil de programmation connu sous le nom de débogueur de mémoire (Memory Debugger), dont le but n’est pas de remplacer un Garbage Collector, mais de le compléter. Dans ce qui suit, nous décrirons l’utilité d’un débogueur de mémoire en élaborant une étude de deux outils qu’on considère les plus pertinents dans le domaine. Mais tout d’abord, on juge important de décrire les différentes erreurs dues à une mauvaise gestion de mémoire qu’un programme pourrait avoir à confronter. On évaluera le degré de gravité de ces erreurs ainsi que leurs manières d’affecter le programme durant son exécution.

Le rapport de stage ou le pfe est un document d’analyse, de synthèse et d’évaluation de votre apprentissage, c’est pour cela chatpfe.com propose le téléchargement des modèles complet de projet de fin d’étude, rapport de stage, mémoire, pfe, thèse, pour connaître la méthodologie à avoir et savoir comment construire les parties d’un projet de fin d’étude.

Table des matières

Chapitrel : Introduction 
1.1. Énoncé de la problématique
L2Pfiy\cipe. Je delation
1.3.Contribution
lAStructure du mémoire
Chapitre 2: Gestion de la mémoire : un état de l’art 
2.1.Gestion du cycle de vie des objets
2.1.1. Introduction 
2.1.2. Gestion de mémoire implicite
2.1.3. Les erreurs dues aune mauvaise gestion de mémoire
2.1.3.1. Les erreurs d’accès (Access Errors)
2.1.3.2. Les fuites de mémoires (Memory Leaks)
2.1.3.3. Les dépassements de tampon (Buffer Overflow)
2.2.Les débogueurs de mémoire dynamiques
2.2.1. Introduction 
2.2.2. Purify
2.2.2.1. Les erreurs d’accès de mémoire (Memory Access Errors)
2.2.2.2. Les erreurs de fuite de mémoire (Memory Leaks Errors)
2.2.2.3. Purify expérimental
2.2.2.4. Résultats
2.2.3. Memcheck
2.2.3.1. Valgrind
2.2.3.2. Valid-value (V) bits
2.2.3.3. Valid-adress (A) bits
2.2.3.4. Notes et limitations
2.3.Gestion de mémoire et C++
2.3.1. Gestion de la mémoire en C++
2.3.2. Problèmes actuels avec les compilateurs C++
2.4.Conclusion 
Chapitre 3: Préoccupations et aspects 
3.1.Introduction 
3.2.Séparation des préoccupations
3.3.Programmation par aspect (POA)
3.3.1. Descriptions
3.3.2. Fonctionnement et Motivations
3.3.3. Intégration des aspects
3.3.4. Programmation par aspect avec AspectC++
3.4.Conclusion 
3.5.Objectif de la recherche
Chapitre 4: Développement d’un outil de gestion implicite et de débogage de mémoire basé sur la programmation par aspect (AspectC++Debogger) 
4.1 .Introduction 
4.2.Gestion de mémoire par aspect
4.2.1. AspectC++
4.2.1.1. Les points de jointure (Join point)
4.2.1.2. Les coupes transverses (Pointcut)
4.2.1.3. Composition de coupes transverses (Pointcut composition)
4.2.1.4. Les coupes transverses nommées (NamedPointcut) 50
4.2.1.5. Les expressions de correspondance (Match expressions)
4.2.1.6. Les conseils (Advice)
4.2.2. Les éléments affectant la consommation de mémoire dynamique en C++.
4.2.2.1. Les membres de la classe JointPoint
4.2.2.2. Exemple de collection de données lors d’une allocation et d’une désallocation
4.2.2.3. Collection des noms de classes
4.3.Outil de gestion et de débogage de mémoire par aspect
4.3.1. Aspect calculant le temps d’exécution de différentes applications
4.3.1.1. Mesure du temps d’exécution
4.3.1.2. Détection des portions de codes à mesurer avec Aspect C+ +
4.3.2. Aspect évaluant la mémoire utilisée lors de l’exécution d’une application
4.3.2.1. Détection des appels de l’opérateur new
4.3.2.2. Changement du comportement de l’opérateur new en allouant deux espaces mémoires 4.3.2.3. Extraction de la taille de l’espace mémoire alloué
4.3.3. Aspect pour sécuriser les allocations et les désallocations de mémoire
4.3.3.1. Allocation sécurisée
a.Extraction du pointeur retourné par l’opérateur new
b. Extraction du nom de fichier et du numéro de ligne
c. Coordination du conteneur avec les allocations dynamiques atteintes dans l’application cliente
d.Initialisation des informations accompagnant toute allocation
e. Initialisation du Tampon Overflow
4.3.3.2. Validité d’un pointeur
4.3.3.3. Désallocation sécurisée d’une zone mémoire
4.3.4. Aspect de détection des erreurs de fuites de mémoire
4.4.Conclusion 
Chapitre 5: Etude de cas et résultats expérimentaux 
5.1.Application à analyser et motivations
5.1.1. Choix de l’application et Motivations
5.1.2. Le Jeu Asteroids 69
5.2.AspectC++Debogger et l’intégration de ses principaux composants
5.2.1. Visual Studio 2005 et AspectC++Add-In
5.2.2. Integration du jeu Asteroids sous Visual Studio 2005
5.2.3. AspectC++ Debogger et manuel d’utilisation
5.3.Expérience et résultats
5.3.1. Les fuites de mémoire
5.3.2. Les erreurs d’accès
5.3.3. Les dépassements de tampon
5.4.Discussion
5.4.1. Habilité de détection et de traitement des différents types d’erreurs de gestion de mémoire C++
5.4.2. Coexistance d’une gestion implicite et explicite de la mémoire
5.4.3. Rlentissement de l’application cliente et diminution de ses performances
5.4.4. Généricité de l’outil
5.4.5. Stabilité d’AspectC++
5.4.5.1.Problèmes de compilateur
5.4.5.2.Templates C++ non reconnus
5.5.Conclusion 
Chapitre 6: Conclusion 
Bibliographie
Annexe

Rapport PFE, mémoire et thèse PDFTélécharger le rapport complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *