chap6-2
Transcription
chap6-2
Chapitre 6 : Pointeurs (suite) 6.8 Paramètres accès type Cellule is record Val : Integer; Suiv : access Cellule; end record; function Somme (L : access Cellule) return Integer is S : Integer := 0; Cour : access Cellule := L; begin while Cour /= null loop S := S + Cour.all.Val; Cour := Cour.all.Suiv; -- dessin après 2 tours end loop; return S; end Somme; Rappel : listes simplement chaı̂nées type Cellule is record Val : Integer; Suiv : access Cellule; end record; 6.7 Une pile non bornée package Pile_Non_Bornee is -- la pile est initialement vide procedure Vider; -- vide la pile procedure Empiler (X : in Integer); -- garantit : X est empilé au sommet de la pile ... Somme (M) ... procedure Depiler (X : out Integer); -- requiert : la pile n’est pas vide -- garantit : le sommet de la pile est dépilé et rangé dans X end Pile_Non_Bornee; L M --> 19 Cour 2 6 S 8 7 4 3 1 La pile est simplement une liste simplement chaı̂née... 6.9 Types accès nommés package body Pile_Non_Bornee is Un paramètre accès est un paramètre de mode “in” ⇒ pour passer un accès en “out” ou “in out”, il faut un nom de type type Cellule is record ... end record; P : access Cellule; -- initialement null => pile vide type Ptr is access Nom-De-Sous-Type; procedure Vider is begin P := null; end Vider; Pour les listes simplement chaı̂nées : type Cellule; type Liste is access Cellule; type Cellule is record Val : Integer; Suiv : Liste; end record; procedure Empiler (X : in Integer) is begin P := new Cellule’(X, P); end Empiler; --> Déclaration incomplète de type --> Seule utilisation autorisée --> Complétion de la déclaration procedure Aj_Tete (L : in out Liste; X : in Integer) is begin L := new Cellule’(X, L); end Aj_Tete; procedure Depiler (X : out Integer) is begin X := P.all.Val; P := P.all.Suiv; end Depiler; Dans les TD on utilisera toujours des types accès nommés end Pile_Non_Bornee; 2 4 Utilisation : 6.10 Des piles non bornées Empiler (R, 1); Empiler (S, 2); Empiler (R, 3); Empiler (S, 4); Depiler (R, V); Paquetage pour un type abstrait Pile : package Piles is type Pile is private; R 3 1 S 4 2 V 3 -- initialement, une pile est vide procedure Vider (P : in out Pile); R := S; -- partage de liste procedure Empiler (P : in out Pile; X : in Integer); R 3 1 S 4 2 V 3 procedure Depiler (P : in out Pile; X : out Integer); private Empiler (S, 5); Depiler (R, V); -- pas de problème ici type Cellule; type Pile is access Cellule; -- La complétion de Cellule peut aller dans le corps de Piles end Piles; ... R, S : Pile; V : Integer; 3 1 S 4 2 V 4 5 7 5 Corps du paquetage : 6.11 Gestion des cellules libérées package body Piles is type Cellule is record Val : Integer; Suiv : Pile; end record; Phénomène de fuite de mémoire (memory leak) : les cellules dépilées sont inaccessibles mais restent allouées → on pourrait les conserver pour les réutiliser ultérieurement ⇒ on les chaı̂ne dans une liste Libres (de type Pile !) Libres : Pile; -- var. globale du corps de Piles procedure Depiler (P : in out Pile; X : out Integer) is A_Liberer : constant Pile := P; begin X := P.all.Val; P := P.all.Suiv; A_Liberer.all.Suiv := Libres; Libres := A_Liberer; end Depiler; procedure Vider (P : in out Pile) is begin P := null; end Vider; procedure Empiler (P : in out Pile; X : in Integer) is begin P := new Cellule’(X, P); end Empiler; procedure Depiler (P : in out Pile; X : out Integer) is begin X := P.all.Val; P := P.all.Suiv; end Depiler; end Piles; R Depiler (S, V); Depiler (S, V); V 4 S Libres 6 5 4 2 A Liberer 8 Pour empiler, on alloue seulement si Libres est vide 6.12 Désallocation des cellules procedure Empiler (P : in out Pile; X : in Integer) is Recup : Pile; begin if Libres = null then P := new Cellule’(X, P); else Recup := Libres; Libres := Libres.all.Suiv; Recup.all := (X, P); P := Recup; end if; end Empiler; Inconvénient de la méthode précédente : s’il n’y aura plus d’Empiler, les cellules de Libres restent allouées ⇒ occupation mémoire inutile Le langage fournit un moyen de désallouer les objets du tas ⇒ ils n’existent plus en tant qu’objets du programme ⇒ la mémoire qu’ils occupaient peut être réutilisée Si T est un nom de (sous-)type et A est défini par type A is access T; alors on peut écrire : with Ada.Unchecked_Deallocation; ... procedure Liberer is new Ada.Unchecked_Deallocation (T, A); Empiler (S, 9); Libres 9 1 et on obtient une procedure Liberer (X : in out A); Un appel Liberer (P); est sans effet si P vaut null Sinon, la mémoire occupée par P.all est désallouée et en sortie P vaut null 2 S 11 9 Un problème V : Integer; R, S : Pile; ... Empiler (R, 1); Empiler (R, 2); S := R; Depiler (R, V); Corps de Piles avec désallocation : -- R : null S : null Libres : null ----- S S S S Libres Libres Libres Libres R R R R : : : : 1 2->1 2->1 1 : : : : null null 2->1 2->1 : : : : null null null 2 with Ada.Unchecked_Deallocation; package body Piles is type Cellule is record ... end record; procedure Empiler (P : in out Pile; X : in Integer) is begin P := new Cellule’(X, P); end Empiler; ??? S pointe vers la cellule 2, dont le Suiv a été mis à null !!! ⇒ Il faut interdire le partage Ici il suffit d’écrire : procedure Liberer is new Ada.Unchecked_Deallocation (Cellule, Pile); package Piles is type Pile is limited private; ... private type Cellule; type Pile is access Cellule; end Piles; procedure Depiler (P : in out Pile; X : out Integer) is A_Liberer : Pile := P; begin X := P.all.Val; P := P.all.Suiv; Liberer (A_Liberer); end Depiler; Les clients du paquetage ne peuvent plus utiliser := sur les Piles. ... mais le type n’est pas limité pour le corps du paquetage end Piles; 10 12 Attention : Liberer (P) ne désalloue que l’objet pointé par P 6.14 Récupération automatique de la mémoire procedure Vider (P : in out Pile) is begin Liberer (P); end Vider; Désallocation mal contrôlée : source d’erreurs (“Unchecked”) Certains langages (Java, Caml...) l’interdisent Ils fournissent en contrepartie un mécanisme de récupération automatique de la mémoire : Ramasse-miettes (Garbage Collector, GC, Glaneur de Cellules) Si P est non vide, seul son sommet est désalloué. ⇒ Il faut écrire : procedure Vider (P : in out Pile) is A_Liberer : Pile; begin while P /= null loop A_Liberer := P; P := P.all.Suiv; Liberer (A_Liberer); end loop; end Vider; Principe simplifié : lorsque le tas devient trop volumineux il est parcouru pour rechercher les objets non accessibles depuis les objets globaux ou ceux dans la PILE et la mémoire correspondante est récupérée Avantage : plus d’erreurs liées à la désallocation explicite Inconvénient : coût en temps lors de l’exécution du GC → peut être un problème pour les programmes temps-réel → n’en est pas vraiment un pour les programmes interactifs (le GC est exécuté pendant les attentes d’entrées) Attention : il faut ici aussi interdire le partage R, S : Pile; ... Empiler (R, 1); Empiler (R, 2); S := R; Depiler (R, ...); -- (ou Vider (R); => R vaut null) → Dans les deux cas, S /= null, mais son sommet est désalloué ! ⇒ Pile doit être limité 13 6.13 Méfaits et bienfaits du partage Note : les compilateurs Ada peuvent fournir un GC mais Gnat n’en fournit pas... 6.15 Passage de paramètre par référence Le partage (2 pointeurs Foo et Bar pointent au même endroit) entraı̂ne une synonymie (aliasing) : Foo.all et Bar.all : deux noms pour le même objet obj ⇒ on peut modifier obj ou toute structure sur laquelle il pointe en utilisant Foo.all (resp. Bar.all), on le retrouvera modifié en y accédant via Bar.all (resp. Foo.all) (nom officiel Ada du passage par variable) Un paramètre passé par référence n’est pas recopié dans la PILE l’adresse de l’objet paramètre effectif est empilée La synonymie peut aussi survenir avec les tableaux : soit T un tableau etI et J deux variables d’indices ; si I = J T (I) := exp; modifie T (J) ⇒ T (I) et T (J) : synonymes Si les Tab sont passés par copie, dans un appel P(T) T et U désignent deux objets différents ⇒ affecter T(I) (resp. U(I)) ne modifie pas U(I) (resp. T(I)) (mais U est recopié dans T en sortie si “out”) Si les Tab sont passés par référence, dans un appel P(T) T et U désignent le même objet ⇒ synonymes ⇒ affecter T(I) (resp. U(I)) modifie U(I) (resp. T(I)) type Tab is array (...) of ...; T : Tab; procedure P (U : [in] [out] Tab) is ... end P; Le partage peut être souhaitable et bénéfique Exemple : on veut accéder à une même pile depuis Foo et Bar type P_Pile is access Pile; -- Note : P_Pile n’est pas limité -- Foo et Bar étant de type P_Pile et V de type Integer : Foo := new Pile; --> une nouvelle Pile (Foo.all) Bar := Foo; --> la m^ eme Pile est aussi nommée Bar.all Empiler (Foo.all, 42); Empiler (Bar.all, 27); Depiler (Foo.all, V); --> V contient 27 Depiler (Bar.all, V); --> V contient 42 15 Si le mode de passage (copie ou référence) n’est pas spécifié, ces affectations rendent le comportement du programme indéfini Note : le mode de passage n’est pas spécifié pour les tableaux... 14 16