Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Gestion dynamique de la mémoire

Les pointeurs Manipulation de listes chaînées


Accès rapide :
Un premier exemple d'allocation dynamique
Utilisation de Valgrind pour détecter les fuites mémoire.
Autres fonctions d'allocation mémoire.


Nous allons dans ce nouveau chapitre nous intéresser à la gestion dynamique de la mémoire. Effectivement, utiliser la pile d'exécution est très pratique : c'est ce qui est fait quand vous déclarez vos variables locales. Mais il ne faut pas oublier que la pile d'exécution est bornée. Si vous dépassez de l'espace de mémoire octroyé pour la pile, un plantage de votre application sera produit.

Pour pallier cette difficulté, il vous est possible d'allouer dynamiquement de la mémoire dans le tas (le heap, en anglais). La mémoire disponible dans le tas est bien plus grande que celle de la pile d'exécution. Par contre, il ne faudra pas oublier de libérer cette mémoire après utilisation, c'est de votre responsabilité. Si vous ne le faite pas, vous pouvez saturer cet espace et encore une fois occasionner un plantage de votre programme. On parle alors de fuites de mémoire (memory leaks en anglais).

Nous finirons par voir qu'il existe des outils pour détecter ces fuites mémoire et notamment l'outil Valgrind.

Un premier exemple d'allocation dynamique

Avant de vous parler d'allocation dynamique, regardez le programme suivant et essayez de deviner ce qui va se passer. Si vous pensez connaitre la réponse, lancez-le.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
#include <stdio.h>
#include <stdlib.h>


int main() {

    char * text = "Toto";
    text[1] = 'a';
    text[3] = 'a';
    printf( "%s\n", text );

    return EXIT_SUCCESS;

}
Tentative de modification de la chaîne

En fait, nous avons déjà parlé de cet exemple précédemment. Ce code ne marche pas. La variable text pointe sur une chaine de caractères constante de votre programme : elle est alors noyée dans la zone de mémoire du code exécutable de votre programme et cette zone de mémoire est READ ONLY. Chercher à modifier cette chaîne de caractères aboutira obligatoirement à un plantage de votre programme.

$> gcc -Wall -o strClone strClone.c
$> strClone
Segmentation fault (core dumped)
$> 

Une solution pour contourner ce problème, consiste à cloner la chaîne de caractères dans un nouvel espace mémoire. Pour allouer ce nouvel espace mémoire nous allons utiliser la fonction malloc : elle est définie dans l'entête standard <stdlib.h>. Bien entendu, il faudra libérer l'espace mémoire une fois le traitement terminé. Utilisez la fonction free pour ce faire.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
#include <stdio.h>
#include <stdlib.h>    // pour avoir malloc et free
#include <string.h>    // pour avoir strcpy et strlen

char * strClone( const char * originalString ) {
    char * newString = (char *) malloc( strlen( originalString ) + 1 );
    strcpy( newString, originalString ); 
    return newString;
}


int main() {

    char * text = strClone( "Toto" );
    text[1] = 'a';
    text[3] = 'a';
    printf( "%s\n", text );

    free( text );

    return EXIT_SUCCESS;

}
Clonage de la chaîne

Quelques explications seront certainement les bienvenues. La fonction strlen permet de calculer la taille d'une chaîne de caractères. Cette fonction ne tient pas compte du zéro terminal : il faut donc considérer une case mémoire de plus pour l'allocation. La fonction malloc permet d'allouer (memory allocation) un nouveau bloc de mémoire dans le tas (le heap). Elle prend en paramètre la taille du bloc de mémoire demandé et renvoie un pointeur (typé void *, un pointeur générique) sur cette nouvelle zone de mémoire.

Une fois le bloc de mémoire obtenu, la fonction strcpy permet de copier les caractères de la chaîne passée en second paramètre dans celle passée en premier paramètre. A ce stade, la chaîne est clonée. Enfin la fonction free, invoquée dans le main, permet de libérer le bloc de mémoire.

Pour de plus amples informations sur ces fonctions vous pouvez consulter les pages d'aide sur ces fonctions : strlen, strcpy, malloc et free.

Attention : tout le monde n'est pas forcément d'accord pour coder ce genre de fonctions (comme notre strClone). Effectivement, il y a une histoire de responsabilité : si une telle fonction était fournie par la librairie standard, il serait de la responsabilité de cette libraire de faire les allocations mémoire alors qui serait de la responsabilité de son utilisateur de libérer cette mémoire. Du coup, un utilisateur d'une telle fonction n'est pas forcément au courant qu'il a la responsabilité de libérer la mémoire. Vous ne trouverez pas de fonction de ce type dans la librairie standard. Par contre, la librairie de programmation système d'un système d'exploitation Unix/Linux en fournit (par exemple, la fonction strdup qui est l'équivalent de notre strClone).

Utilisation de Valgrind pour détecter les fuites mémoire.

Reprenons, l'exemple précédent et commentons la ligne de code qui libère le bloc de mémoire. Nous avons maintenant entre les mains, un programme produisant des fuites mémoires. Ce bug, répété à grande échelle est très problématique : dans ce cas, celui-ci ne tiendra peut-être pas plus que quelques minutes.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
#include <stdio.h>
#include <stdlib.h>    // pour avoir malloc et free
#include <string.h>    // pour avoir strcpy et strlen

char * strClone( const char * originalString ) {
    char * newString = (char *) malloc( strlen( originalString ) + 1 );
    strcpy( newString, originalString ); 
    return newString;
}


int main() {

    char * text = strClone( "Toto" );
    text[1] = 'a';
    text[3] = 'a';
    printf( "%s\n", text );

    //free( text );

    return EXIT_SUCCESS;

}
Clonage de la chaîne

La question que vous devriez vous poser est : comment détecter une fuite mémoire ? Pour ce faire, un outil sur puissant vous est proposé : Valgrind. Par contre, sachez que Valgrind ne fonctionne que sur système Unix/Linux. Il existe d'autres outils équivalents que l'on trouve facilement sur Internet. Pour installer Valgrind, utilisez votre gestionnaire de logiciels de votre système d'exploitation (par exemple, sudo apt-get install valgrind sur Debian ou Ubuntu).

Pour pleinement tirer profit de Valgrind, veuillez compiler votre programme avec l'option -g pour compiler en mode debug.

$> gcc -Wall -g -o strClone strClone.c
$> valgrind strClone
==9343== Memcheck, a memory error detector
==9343== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9343== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==9343== Command: strClone
==9343== 
Tata
==9343== 
==9343== HEAP SUMMARY:
==9343==     in use at exit: 5 bytes in 1 blocks
==9343==   total heap usage: 2 allocs, 1 frees, 1,029 bytes allocated
==9343== 
==9343== LEAK SUMMARY:
==9343==    definitely lost: 5 bytes in 1 blocks
==9343==    indirectly lost: 0 bytes in 0 blocks
==9343==      possibly lost: 0 bytes in 0 blocks
==9343==    still reachable: 0 bytes in 0 blocks
==9343==         suppressed: 0 bytes in 0 blocks
==9343== Rerun with --leak-check=full to see details of leaked memory
==9343== 
==9343== For counts of detected and suppressed errors, rerun with: -v
==9343== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$> 

Comme vous pouvez le voir, une fuite de mémoire, en un bloc de 5 octets, est détectée. C'est bien la bonne taille. Et comme indiqué dans le rapport, vous pouvez obtenir plus d'informations en ajoutant l'option --leak-check=full à la ligne de commande. Testons cette possibilité :

$> valgrind --leak-check=full strClone
==9370== Memcheck, a memory error detector
==9370== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9370== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==9370== Command: strClone
==9370== 
Tata
==9370== 
==9370== HEAP SUMMARY:
==9370==     in use at exit: 5 bytes in 1 blocks
==9370==   total heap usage: 2 allocs, 1 frees, 1,029 bytes allocated
==9370== 
==9370== 5 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9370==    at 0x4C2BBAD: malloc (vg_replace_malloc.c:299)
==9370==    by 0x400619: strClone (strClone.c:6)
==9370==    by 0x400648: main (strClone.c:14)
==9370== 
==9370== LEAK SUMMARY:
==9370==    definitely lost: 5 bytes in 1 blocks
==9370==    indirectly lost: 0 bytes in 0 blocks
==9370==      possibly lost: 0 bytes in 0 blocks
==9370==    still reachable: 0 bytes in 0 blocks
==9370==         suppressed: 0 bytes in 0 blocks
==9370== 
==9370== For counts of detected and suppressed errors, rerun with: -v
==9370== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
$> 

L'outil devient juste exceptionnel : nous avons une fuite mémoire et le bloc de mémoire en cause a été alloué en ligne 6 de notre fichier de code. Il ne vous reste plus qu'à corriger le problème. Je vous invite très fortement à utiliser Valgrind (ou un outil équivalent) pour détecter ce type de bugs.

Noter aussi que Valgrind peut trouver une multitude d'autres types de disfonctionnements. Pour de plus amples informations sur ce logiciel, vous pouvez consulter notre support de cours sur l'utilisation de Valgrind.

Autres fonctions d'allocation mémoire.

Deux autres fonctions peuvent être utiles pour la gestion de la mémoire :



Les pointeurs Manipulation de listes chaînées