Précédent Index Suivant

Allocation et récupération mémoire

La plupart des langages permettent d'allouer dynamiquement de la mémoire. On peut citer C, Pascal, Lisp, ML, SmallTalk, C++, Java, ADA.

Allocation explicite

On distingue deux types d'allocation : Le premier cas est illustré par les fonction new de Pascal ou malloc de C. Elles retournent un pointeur sur une zone mémoire (i.e. son adresse), on peut alors accéder à cette zone pour consulter ou modifier la valeur qu'elle contient. Le deuxième cas correspond aux fonctions de construction de valeurs d'Objective CAML ou celle des langages à objets. Les instances de classes des langages objets sont construites en passant à un opérateur new, la fonction de construction particulière à la classe qui, elle-même attend, en général, un certains nombre de paramètres d'initialisation. Pour les langages fonctionnels, les fonctions de constructions sont immédiatement utilisées dès que l'on définit une valeur structurée (un couple, une liste, un enregistrement, un vecteur, une fermeture).

Examinons la construction de valeurs Objective CAML sur un exemple. Leur représentation mémoire est illustrée par le schéma de la figure 9.1.


Figure 9.1 : représentation mémoire de valeurs



# let u = let l = ['c'; 'a'; 'm'] in List.tl l ;;
val u : char list = ['a'; 'm']
# let v = let r = ( ['z'] , u )
in match r with p -> (fst p) @ (snd p) ;;
val v : char list = ['z'; 'a'; 'm']
On a représenté un élément de liste par un doublet de deux mots, le premier contenant un caractère et le second un pointeur vers l'élément suivant de la liste. La représentation réelle est légèrement différente. Elle est détaillée dans le chapitre sur l'interface avec C (voir page ??).

La première phrase construit une valeur, nommée l, en allouant une cellule (constructeur ::) pour chaque élément de la liste ['c';'a';'m']. La déclaration globale u correspond à la queue de l. Il y a ici un partage entre l et u, c'est-à-dire entre l'argument et le résultat de l'application de List.tl. Seule la déclaration u est connue après l'évaluation de cette première phrase (l cesse d'exister).

La deuxième phrase construit une liste à un seul élément, puis un couple nommé r contenant cette liste et la liste u. Ce couple est filtré et renommé p par le filtrage. Ensuite, le premier élément de p est concaténé avec son second élément, ce qui crée une valeur ['z';'a';'m'] liée à l'identificateur global v. On observe que le résultat de snd (la liste ['a';'m']) est partagé avec son argument p alors que le résultat de fst (le caractère 'z') est copié.

Dans tous les cas l'allocation mémoire est explicite. C'est-à-dire qu'elle est demandée par le programmeur (comme instruction ou expression du langage).

Remarque


L'allocation mémoire conserve une information sur la taille de l'objet alloué dans le but de pouvoir le libérer par la suite.


Récupération explicite

Les langages dont la récupération mémoire est explicite possèdent un opérateur de libération (free en C ou dispose en Pascal) qui reçoit l'adresse (un pointeur) de la zone à désallouer. En utilisant les informations conservées au moment de l'allocation, le programme libère cette zone et pourra la réutiliser par la suite.

L'allocation dynamique est en général utilisée pour manipuler des données structurées évolutives telles les listes, les arbres, etc. Libérer l'espace occupé par de telles données ne se fait pas en << un coup de cuillère à pot >> mais réclame une fonction de parcours de la donnée. On appelle de telles fonctions des destructeurs.

Si la définition correcte des fonctions de destruction ne pose pas trop de difficulté, leur usage est, en revanche, plus délicat. En effet, pour libérer l'espace occupée par une structure, il faut en parcourir les pointeurs et appliquer l'opérateur de libération mémoire du langage. Laisser la libération de la mémoire à la charge du programmeur présente l'avantage que ce dernier est sûr des actions effectuées. Néanmoins une mauvaise utilisation de ces opérateurs peut provoquer une erreur à l'exécution du programme. Les principaux dangers de la récupération explicite sont : C'est toute la difficulté de la récupération explicite de mémoire que de connaître la durée de vie de l'ensemble des valeurs d'un programme.

Récupération implicite

Les langages avec récupération implicite ne possèdent pas d'opérateurs de libération mémoire. Il n'est pas possible pour le programmeur de décréter la disparition d'une valeur allouée. Néanmoins, un mécanisme de récupération automatique est déclenché quand une valeur n'est plus référencée, ou lors d'un échec de l'allocation, c'est-à-dire quand le tas est plein.

Un algorithme de récupération automatique de mémoire est en quelque sorte un destructeur global. Ce caractère rend sans doute plus difficile sa conception et son implantation que le seraient celles d'un destructeur dédié à une structure de données particulière. Mais, une fois cette difficulté surmontée, la fonction de récupération obtenue accroît considérablement la sûreté de la gestion mémoire. En particulier le risque de pointeurs fantômes disparaît.

D'autre part, une fonction de récupération automatique de mémoire peut apporter de bonnes propriétés sur le tas : Les choix de conception d'un GC doivent tenir compte d'un certain nombre de critères et de contraintes : En pratique, le critère de sûreté reste primordial, et les GC trouvent un compromis concernant les autres contraintes.


Précédent Index Suivant