Etats de pointeur ´
Un pointeur peut ˆetre dans les ´etats suivants:
• NULL
: valeur sp ´eciale qui ne pointe sur rien•
fou (dangling): l’objet point ´e n’existe plus•
fuite (leak): l’objet point ´e n’est plus n ´ecessaire•
normalLes pointeurs fous et les fuites sont les deux probl `emes fondamentaux li ´es aux pointeurs.
Gestion m ´emoire
Allocation (facile) d ´esallocation (aha!) Comme les boˆıtes `a vitesse:
•
Automatique: la d ´esallocation est prise en charge par le langage•
Manuelle: la d ´esallocation est `a la charge du programmeur•
Semi-automatique: le langage v ´erifie les d ´esallocations Granularit ´e:•
Par objet: chaque objet est allou ´e/d ´esallou ´e individuellementGestion m ´emoire manuelle en C
Deux fonctions de la biblioth `eque standard:
void *malloc (int n);
void free (void *ptr);
Fonctions typiquement implant ´ees en C; rien de sp ´ecial!
Utilisent des primitives du SE pour obtenir de la m ´emoire, e.g.
mmap
Ces fonctions partagent des informations administratives internes E.g. une free list de zones m ´emoire encore disponibles
free
peut avoir besoin de savoir combien de bytes sont lib ´er ´es⇒
la taillen
peut ˆetre stock ´ee parmalloc
juste avant lesn
bytesGestion m ´emoire par r ´egion
Au lieu de 2 op ´eration allouer N bytes et lib ´erer ces bytes:
region *region_new (void);
void *region_alloc (int n, region *r);
void region_free (region *r)
La cr ´eation de r ´egion parfois rec¸oit une information de taille
Un objet encore n ´ecessaire emp ˆeche toute sa r ´egion d’ ˆetre lib ´er ´ee
region alloc
plus efficace quemalloc
Gestion m ´emoire automatique
•
Comptage de r ´ef ´erences: `a chaque objet est associ ´e un compteur qui indique combien de pointeurs existent. Lorsque le compteur passe `a 0, on peut d ´esallouer l’objet.•
GC (Glanage de Cellules ou plut ˆot Garbage Collection): `a partir des racines (i.e. les variables globales et la pile), traverser tous lesobjets atteignables en passant par tous les pointeurs: les objets non-visit ´es peuvent ˆetre d ´esallou ´es.
•
R ´egions: une analyse sophistiqu ´ee du code d ´etermine dans quelle r ´egion allouer chaque objet, et `a quel moment d ´esallouer chaque r ´egion.Compter les r ´ef ´erences
Chaque objet contient un champ refcnt
refcnt compte les r ´ef ´erences “entrantes” (qui pointent sur cet objet)
Copie resp. destruction de pointeur incr ´emente resp. d ´ecr ´emente refcnt Quand refcnt
= 0
:•
D ´ecr ´ementer les refcnt des objets point ´es•
Lib ´erer l’objetSimple `a impl ´ementer, r ´ecup ´eration prompte, mais co ˆuteux
Difficult ´e de compter les r ´ef ´erences
Attention `a d ´ecr ´ementer apr `es incr ´ementer Co ˆut de tous ces incr ´ements et d ´ecr ´ements
Synchronizer les incr ´ements et d ´ecr ´ements en cas de concurrence Incapable de r ´ecuperer les cycles
Mark&Sweep
Chaque objet contient un markbit qui indique si l’objet est accessible Commencer par marquer tous les objets comme “inaccessibles”
Mark: Marquer r ´ecursivement tous les objets accessibles
•
Commencer par les racines•
Suivre tous les pointeurs des objets rencontr ´esSweep: R ´ecup ´erer tous les objets encore marqu ´es “inaccessible”
Mark (&Sweep)
mark (ptr) {
if (!ptr->marked) {
ptr->marked = True;
for i = 0 to ptr->size mark (ptr[i]);
} }
mark_all () {
for varptr in roots mark (*varptr);
}
(Mark&) Sweep
sweep_all () {
ptr = heap_start;
do {
if (ptr->marked)
ptr->marked = False;
else
free_object (ptr);
} while (ptr = next_object (ptr))
}
Stop&Copy
Alloue un nouveau tas To aussi grand que le tas actuel From
•
alloc ptr indique quelle partie de To est encore libre•
scan ptr indique quelle partie de To est termin ´eeCopy: copier tous les objets accessibles de From dans To
•
Commencer par les racines•
Copier les objets trouv ´es (cela fait avancer alloc ptr)•
Placer un forwarding pointer de l’original vers la copie•
Suivre tous les pointeurs entre scan ptr et alloc ptrUne fois termin ´e, on peut lib ´erer From d’un seul coup d’un seul
(Stop&)Copy
copy (ptr) {
if (ptr->forward = NULL) { for i = 0 to ptr->size
alloc_ptr[i] = ptr[i];
ptr->forward = alloc_ptr;
alloc_ptr += ptr->size;
}
return ptr->forward;
}
Stop&Copy
stop© () {
alloc_ptr = scan_ptr = alloc_new_heap ();
for varptr in roots
*varptr = copy (*varptr);
while (scan_ptr < alloc_ptr) { for i = 0 to scan_ptr->size
scan_ptr[i] = copy (scan_ptr[i]);
scan_ptr += scan_ptr->size;
}
free_old_heap ();
}
Besoins du collecteur
Refcount Mark&Sweep Stop&Copy
champ refcnt (1-32bit) champ markbit (1bit) champ forward (1bit) Obtenir la taille de n’importe quel objet
Savoir quels champs contiennent des pointeurs inc/dec copie de ptr Liste de toutes les racines
Acc `es au tas Stop the world
Stop the world
mutateur: Le programme principal, qui fait le “travail utile”
collecteur: Le code qui s’occupe de r ´ecup ´erer la m ´emoire inutilis ´ee M&S et S&C sont tous deux des algorithmes stop the world:
Le mutateur doit ˆetre stopp ´e pendant que travaille le collecteur
Famille de GC
Un GC peut- ˆetre:
•
incr ´emental: chaque phase de GC est d ´ecoup ´ee en petite tranches•
concurrent: mutateur et collecteur concurrents•
parall `ele: collecteur divis ´es en plusieurs threads•
partitionn ´e: le tas est divis ´e en sous-tas collect ´es ind ´ependamment•
g ´en ´erationnel: GC partitionn ´e en sous-tas ordonn ´es par ˆage•
distribu ´e: GC partitionn ´e sur des machines diff ´erentesFinalization, pointeurs faibles
Les syst `emes `a base de GC offrent souvent la possibilit ´e de d ´etecter quand un objet est d ´esallou ´e:
•
Finalization: le programme sp ´ecifie qu’avant de d ´esallouer l’objet X, il faut ex ´ecuter la fonction F•
Pointeur faible: pointeur qui n’emp ˆeche pas le GC de d ´esallouer l’objet point ´e. A chaque usage du pointeur, il faut v ´erifier s’il est encore vivantUtilis ´es typiquement dans les caches, ou lors d’interaction avec des librairies externes que le GC ne comprend pas
Attention: la d ´esallocation n’a pas forc ´ement lieu
D ´esallocation manuelle
N ´ecessite des conventions et de la discipline E.g. biblioth `eque de table de hachage:
•
Danshash remove
, faut-il d ´esallouer la valeur enlev ´ee?•
Danshash freetable
, faut-il aussi d ´esallouer les valeurs?•
Danshash copytable
, que faut-il faire des valeurs?•
Comment d ´esallouer les valeurs?Pas de r ´eponses universellement id ´eales
D ´esallocation par ownership
1. Un des pointeurs de chaque objet est d ´esign ´e possesseur (owner) 2. L’objet est d ´esallou ´e lorsque son possesseur disparaˆıt
3.
⇒
Un pointeur n’est valide que si le possesseur est valide⇐
Si l’invariant ne peut pas ˆetre pr ´eserv ´e:
•
Faire des copies, chaque copie a son propre possesseur•
Ajouter un compteurs de r ´ef ´erences (compte nb de possesseurs)•
Ajouter un pointeur dont le seul r ˆole est d’ ˆetre le possesseur Le possesseur peut changer au cours du tempsGestion m ´emoire semi-automatique
Gestion manuelle:
•
Source intarissable de bugs graves•
Frein au d ´eploiement de biblioth `eques Gestion automatique:•
Pauses ind ´esirables pour usage temps-r ´eel•
Parfois couteux, parfois inefficace•
Contraintes fortes sur l’ensemble du syst `emeLangage Rust
Sorte de m ´elange de Haskell et de C:
•
C: Langage de bas niveau•
C: Gestion m ´emoire explicite•
C: Langage imp ´eratif, avec r ´ef ´erences explicites•
H: Encourage l’immutabilit ´e•
H: Offre les types alg ´ebriques•
H: Typage statique fort•
H: Classes de type (appel ´ees Traits) [ Note: Exemples tir ´es du manuel de Rust ]Syntaxe de Rust
Haskell Rust
f x y = e fn f (x : τ
1, y : τ
2) → τ {e}
let x = e
1in e
2let x = e
1; e
2if e then e
1else e
2if e then {e
1} else {e
2}
while e
1{e
2}
Ownership sur les chaˆınes
fn
main() {
let s
1=
String::from( "hello" );
let s
2= s
1;
println!
( "s = {} " , s
2);
}
Apr `es le
let s
2= s
1;
, la variables
1 n’est plus utilisable La chaˆıne est d ´esallou ´ee `a la fin de la fonctionTransfert d’ownership
fn
prs(
mys:
String) {
println!( "mys = {} " ,
mys); } fn
main() {
let s =
String::from( "hello" );
prs
(s);
println!
( "s = {} " , s);
}
Erreur! La variable
s
n’est plus valide apr `es prs(s);
Valeurs sans ownership
Certains types n’ont pas besoin de gestion m ´emoire:
fn
main() {
let x
1= 16;
let x
2= x
1;
println!
( "x = {} " , x
1+ x
2);
}
x
1 est encore valide apr `eslet x
2= x
1;
La diff ´erence est que les types entiers impl ´ementent le traits Copy
R ´ef ´erences et pr ˆets
Pour permettre acc `es sans transf ´erer le ownership
fn
prs(
mys:
&String) {
println!( "mys = {} " ,
mys); }
fn
main() {
let s =
String::from( "hello" );
prs
(&s);
println!
( "s = {} " , s);
}
Morceaux de tableaux
Au lieu de pointeurs au milieu des tableaux, Rust offre les slices E.g. le type &str d ´ecrit une r ´ef ´erence sur une sous-chaˆıne:
fn
main() {
let s :
String=
String::from( "Hello" );
let
sub:
&str= &s[0..4];
println!
( "sub = {} " ,
sub);
}
Le type Option
Au lieu de NULL, utilise le type pr ´ed ´efini Option
enum
Option<T > {
Some(T ),
None}
fn
plus one(x :
Option<
i32>) →
Option<
i32> { match x {
None
⇒
None,
Some(i)
⇒
Some(i + 1)
} }
Contr ˆ ole des mutations
Rust impose un contr ˆole sur les modifications des objets
•
Pas de modifications via une r ´ef ´erence de type&T
•
Une r ´ef ´erence de type& mut T
permet les modifications•
Une seule r ´ef ´erence de type& mut T
`a la fois•
Pas de& mut T
et&T
en m ˆeme temps⇒
Pas de probl `emes caus ´es par des aliasThe mot-cl ´e mut
Un objet de type Vec
<
i32>
, est immuable Cependant:fn fill_vec (v1: Vec<i32>) -> Vec<i32> { let mut v2 = v1;
v2.push(42);
v2 }
Le
let
a chang ´e le type enmut
Vec<
i32>
Eviter les r ´ef ´erences folles? ´
fn
test() →
&String{
let s =
String::from( "Hello" );
&s }
fn
main() { let r =
test(); }
La chaˆıne
s
est d ´esallou ´ee `a la fin de testLe compilateur rejette le programme car
&s
survit son owner On peut renvoyers
`a la place (en ajustant le type de test)Lifetimes
let s
1=
String::from( "Hello" );
let
result{ let s
2=
String::from( "Goodbye" );
let l =
longest(&s
1, &s
2);
result
= l;
Interdire result
= l;
pour ´eviter une r ´ef ´erence qui disparaˆıt trop tard!Il faut d ´eterminer la lifetime de
l
:Lifetimes explicites
La fonction longest doit explicitement d ´ecrire ses lifetimes:
fn
longest<
’a> (x :
&’a String, y :
&’a String) →
&’a String{ if x.
len() > y.
len() then {x} else {y } }
Ces annotations de ’a indiquent:
Le lifetime de la valeur de retour est ´egale
au plus grand lifetime commun `a ceux de
x
ety
conversion automatique de
&
’aT
en&
’bT
(si ’b<
’a) Sorte de sous-typageElision des lifetimes ´
En fait, toutes les r ´ef ´erences ont un type de la forme
&
’aT
C’est la gestion et l’inf ´erence de ces lifetimes qui v ´erifie:
Un pointeur n’est valide que si le possesseur est valide R ´ef ´erences copiables vers un type avec une lifetime plus courte C¸ a prend un peu de pratique, mais:
•
La difficult ´e n’est pas artificielle: le m ˆeme probl `eme existe en C•
Le compilateur Rust nous aide plus que les core-dump de CRust
Premier langage populaire d’une longue ligne de recherche
M ´elange inhabituel de fonctionalit ´es de haut-niveau et de bas-niveau Ownership utilis ´e pour:
•
Gestion m ´emoire: but original principal•
Contr ˆoler la mutabilit ´e:– Eviter les probl `emes li ´es aux alias´
– Plus important, ´eviter les conditions de courses