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