fuite de mémoire

Transcription

fuite de mémoire
Prog. II MT & EL
Gestion des pointeurs
Objectifs:
• Maîtriser la gestion des pointeurs en identifiant les
risques liés à l'allocation dynamique de mémoire
Plan:
–
–
–
–
le bon usage des pointeurs
fuite de mémoire
corruption de la mémoire
copie et libération de structure allouée
dynamiquement
1/
Prog. II MT & EL
Le bon usage des pointeurs (1)
Allocation dynamique
-> pointeurs
-> bugs si mal utilisés
On s'épargnera des heures de recherche de bug en adoptant :
- les quelques règles déjà présentées au chapitre 7:
- Initialisation avec NULL ou avec une adresse valide
- test si différent de NULL avant de déréférencer avec *
- les règles supplémentaires concernant l'allocation dynamique
Conséquences possibles d'un bug sur un pointeur :
- Ne pas savoir qu'il y a un bug jusqu'au jour où le prof l'essaie
- Le programme se plante sans rapport apparent avec le bug
Prog. II MT & EL
Le bon usage des pointeurs (2)
1) Initialiser un pointeur avec NULL à sa déclaration, ou avec une
adresse valide
int * p = NULL;
2) Allocation Tester si l'adresse renvoyée par malloc (et autres)
est égale à NULL. Dans ce cas afficher un message d'erreur et
quitter l'exécution.
if( (p = malloc(20*sizeof(int))) == NULL)
{
printf("No memory for %s\n", __func__ );
exit(EXIT_FAILURE);
}
Prog. II MT & EL
Le bon usage des pointeurs (3)
3) Libération Tester si un pointeur est différent de NULL avant de
libérer la mémoire avec free()
if( p != NULL)
{
free(p);
p = NULL;
}
4) Re-initialiser un pointeur à NULL après avoir libéré le bloc de
mémoire avec free() (exemple ci-dessus)
En effet, un pointeur peut servir plusieurs fois au cours de
l’exécution d’un programme , par moment il pointe sur un
espace mémoire valide, sinon il doit être remis à NULL pour
être désactivé.
Libérer
11.4 plusieurs fois le même bloc = comportement indéterminé
Prog. II MT & EL
Le bon usage des pointeurs (4)
5) Utilisation Tester si un pointeur est différent de NULL avant de
l'utiliser : ce test est nécessaire chaque fois que le pointeur
peut avoir été modifié depuis le dernier test de sa valeur.
if(p != NULL) { code utilisant p ; }
Evaluation paresseuse de l’opérateur logique && :
if((p != NULL) && (autre condition utilisant p ))
{
code utilisant p ;
}
if( p && (autre condition utilisant p ))
{
code utilisant p ;
}
Prog. II MT & EL
Fuite de mémoire = Memory leak (1)
La taille du tas est limitée: il est important de restituer la mémoire
dont le programme n'a plus besoin
Scénario classique d'une fuite de mémoire
- Mémoriser l'adresse d'un bloc dans une variable locale qui
n'existe plus lorsque la fonction se termine
void f()
{ int* tab = malloc(12*sizeof(int));
// utilisation de tab comme tableau de 12 int
// oubli de libérer l'espace alloué avec free
}
- conséquences:
1) perte de l'adresse du bloc après avoir quitté f()
2) donc impossibilité de restituer le bloc avec free( )
3) moins de mémoire disponible pour malloc( )
Prog. II MT & EL
Fuite de mémoire = Memory leak (2)
Tas vide
Après
plusieurs
malloc()
si free() n'est pas appelé ou si
les adresses des blocs sont
perdues, la quantité de mémoire
diminue pour les malloc()
suivants ...
Prog. II MT & EL
Fuite de mémoire = Memory leak (3)
Solutions
-
libérer la mémoire avant de quitter la fonction
-
mémoriser/transmettre l'adresse de façon à pouvoir libérer la
mémoire plus tard pendant l'exécution du programme
// type opaque JMA (Jour Mois Annee). Dans jma.h:
JMA * jma_creation(int jour,int mois,int annee);
void jma_destruction(JMA *date);
int
jma_nb_jours_entre_deux_dates(JMA *, JMA *);
1
2
3
4
5
...
JMA * p1 = jma_creation(1,1,1600);
JMA * p2 = jma_creation(1,1,2008);
... jma_nb_jours_entre_deux_dates(p1, p2);
jma_destruction(p1); p1=NULL ;
jma_destruction(p2); p2=NULL ;
Prog. II MT & EL
Fuite de mémoire = Memory leak (4)
Solutions possibles
- Alternative partielle à l'allocation dynamique en C99 :
- Variable Length Array (VLA)
- Pour un tableau utilisé localement dans une fonction
- Equivalent à une "allocation dynamique" sur la pile
void f(int n) // OK
{ int tab1 [n];
// utilisation de tab1 comme tableau de n int
// libération automatique en fin de fonction
}
int * g(int n) // attention: un VLA est LOCAL
{ int tab2 [n];
// utilisation de tab2 comme tableau de n int
return tab2 ; // incorrect !!
} 11.9
Prog. II MT & EL
Corruption de la mémoire (1)
cas où un seul pointeur mémorise l'adresse d'un bloc
Cause: un pointeur devient invalide si on oublie de le ré-initialiser
à NULL après la libération d'un bloc
On parle de: pointeur sauvage ou "en l'air" (dangling)
Il faut toujours appliquer les régles 1 à 5
Conséquence possible: le pointeur accède à une zone mémoire
qui peut être ré-allouée par malloc( ) à un autre pointeur.
Prog. II MT & EL
Corruption de la mémoire (2)
p1 devient un pointeur sauvage après la libération du bloc
Tas
char * p1 = malloc(24);
char * p2 = NULL;
p1
p2
NULL
p1
free(p1);
p2
p2 = malloc(16);
*p1 = 'x' ;
NULL
x
p1
p2
Prog. II MT & EL
Corruption de la mémoire (3)
plusieurs pointeurs mémorisent l'adresse d'un bloc
Cause: si plusieurs pointeurs pointent sur un même bloc alloué
dynamiquement,
alors ils doivent tous être mis à NULL
quand le bloc est libéré
(généralisation du cas précédent)
exemple: très courant pour éviter la duplication d'une structure de
données coûteuse en mémoire: un ensemble d'ETUDIANTs
partage un certain nombre de COURS;
chaque struct ETUDIANT contient des pointeurs vers les
struct COURS plutôt que des champs de type struct COURS
Prog. II MT & EL
Corruption de la mémoire (4)
p2 devient un pointeur sauvage après la libération du bloc
char * p1 = malloc(24);
Char * p2 = p1;
p1
p2
free(p1);
p1 = NULL;
p1
NULL
p2
*p2 = 'x' ;
p1
p2
NULL
x
Tas
Prog. II MT & EL
Copie d'une structure (1)
Copie superficielle ou copie profonde ?
Contexte: une structure s1 contient un pointeur qui pointe sur un
bloc alloué dynamiquement.
Que se passe t'il si j'exécute: s2 = s1; ?
Copie superficielle: l'affectation réalise une copie superficielle, la
valeur de chaque champ de s1 est copiée dans le champ
correspondant de s2.
Conséquence: la valeur du champ pointeur est la même pour s1
et pour s2
Prog. II MT & EL
Copie d'une structure (2)
copie superficielle: s1 et s2 pointent sur le même bloc après
l'affectation s2 = s1
s1
s1
s2
p
p
p
avant
après
Prog. II MT & EL
Copie d'une structure (3)
Copie superficielle ou copie profonde ?
Copie profonde: si on ne veut pas que les structures partagent
les blocs alloués dynamiquement alors il faut gérer soi-même
une copie profonde:
1) Faire l'affectation normale (copie superficielle)
2) faire une allocation dynamique pour le bloc pointé par s2
3) Copier le contenu du bloc pointé par s1 pour initialiser le
nouveau bloc pointé par s2
Prog. II MT & EL
Copie d'une structure (2)
copie profonde
s1
s1
s2
1)
p
p
p
2)
3)
avant
après
Prog. II MT & EL
Libération d'une structure (1)
Libération partielle ou complète ?
Contexte: un pointeur ps1 pointe sur une structure allouée
dynamiquement. Celle-ci contient un pointeur qui pointe sur un
bloc alloué dynamiquement.
Que se passe t'il si j'exécute: free(ps1); ?
Libération partielle: free(ps1) restitue le bloc pointé par ps1, rien
de plus, même si un champ pointe vers un autre bloc alloué
dynamiquement
Conséquence: si l'autre bloc n'est pas partagé par un autre
pointeur, on perd son adresse et donc on ne pourra plus le
libérer.
-> fuite de mémoire
Prog. II MT & EL
libération d'une structure (2)
libération partielle (fuite de mémoire)
ps1
ps1
free(ps1);
ps1=NULL;
NULL
p
A
A
après !
Prog. II MT & EL
Libération d'une structure (3)
Libération partielle ou complète ?
Libération complète: si on veut une libération complète il faut:
1) d'abord libérer le (ou les) bloc(s) pointé(s) par la structure
ex: si un bloc est pointé par le champ p
d'abord faire free (ps1->p);
2) Libérer la structure en dernier avec free(ps1) ;
Remarque: la gestion de liste simplement chaînée traitée dans le
thème 9 décrit comment préserver la cohérence de la liste
lorsqu'on enlève un élément quelconque de la liste
Prog. II MT & EL
libération d'une structure (4)
libération complète
1) free(ps1->p); // rend le bloc A
2) free(ps1);
ps1=NULL;
ps1
ps1
ps1
NULL
p
p
A
avant
1)
après
2)
Prog. II MT & EL
Résumé
• Bon usage des pointeurs = moins de bugs = gain de temps :
– Initialisation des pointeurs avec NULL ou adresse valide
– Test d'un pointeur avant usage : doit être différent de NULL
– Ré-Affectation à NULL s'il est temporairement inactif
• Vigilance supplémentaire pour les cas suivants:
– Bloc mémoire partagé (= pointé par plusieurs pointeurs)
– Bloc mémoire en cascade (= pointeur dans un bloc alloué)
• Conséquences facheuses sinon:
• Perte de mémoire
• Pointeur sauvage