next up previous contents index
suivant: Généricité et modularité monter: main précédent: Clonage d'objets   Table des matières   Index


Libération et glanage d'objets

Rappelons qu'il y a trois modes d'allocation d'un objet : statique, automatique, et dynamique. L'allocation automatique concerne les objets locaux, qui ne sont pas déclarés static ; ils sont alors automatiquement dés-alloués, c'est-à-dire détruits, en même temps que le bloc d'activation de la fonction qui les a alloués. Les deux autres modes d'allocation n'impliquent pas de dés-allocation avant la fin de l'exécution du programme. Ceci n'est pas un problème pour les objets statiques, qui sont complètement connus avant l'exécution de la fonction main du programme. Par contre, comme les objets dynamiques sont créés en cours d'exécution, il est facile, d'une part, d'excéder les ressources en mémoire du processus, et d'autre part, de conserver des objets dynamiques qui ne sont plus utiles.

Certains langages (Lisp, CAML, Java) sont capables de détecter les objets dynamiques inutiles (plus exactement, inaccessibles) et de les dés-allouer afin de remettre la mémoire qu'ils occupent à la disposition de la procédure d'allocation : ces langages disposent d'un glaneur de cellules26. D'autres (Pascal, C, C++), qui n'en disposent pas, font porter la responsabilité de cette dés-allocation sur le programmeur.

Quand un objet alloué dynamiquement n'est plus utile, le programme doit comporter une dés-allocation explicite, au moyen d'une fonction de libération ou de destruction. C++ dispose des opérateurs delete et delete[] qui s'appliquent à l'adresse du bloc dynamique à dés-allouer. Si un objet de type $T$ a été alloué par new $T$, c'est delete qui doit être appelé pour le dés-allouer ; si un tableau d'objets a été alloué par new $T$[$n$], c'est delete[] qui doit être appelé.

Dans le cas d'un objet alloué dynamiquement par morceaux (et non en un seul bloc), ce qui est le cas des structures chaînées, il faut programmer une fonction de libération spécialisée, ou destructeur. Prenons l'exemple des arbres, dont la représentation en mémoire est formée de plusieurs cellules non contiguës en mémoire. La fonction suivante recourt à un parcours en profondeur, avec l'ordre suffixe ; la racine est libérée par delete seulement quand les deux sous-arbres ont été libérés (les ordres infixe et préfixe conduiraient à couper prématurément une ou deux branches non encore traitées) :


void tree_delete(tree p) {
  if (!is_empty_tree(p)) {
      tree_delete(p->left);
      tree_delete(p->right);
      delete p;
    } 
}

Le glanage proprement dit consiste à insérer dans les différentes fonctions du programme des appels aux destructeurs (delete ou des fonctions spécialisées) afin de libérer les objets devenus inutiles. Cette tâche, toujours délicate, peut conduire à des erreurs quand le glanage est excessif, par exemple dans le cas de partage de sous-objets (voir figure 25, page [*]). Il y a cependant des cas où elle peut être réalisée de façon systématique par le programmeur. Ainsi, en cas d'allocation dynamique de tableaux, c'est à la fonction qui les crée de libérer ces tableaux dynamiques avant de retourner :


void f(int m) {
  double *t = new double[m];     // construction
  
   // ...
  delete[] t;                    // destruction
}


next up previous contents index
suivant: Généricité et modularité monter: main précédent: Clonage d'objets   Table des matières   Index
R Lalement