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

Documents pareils