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.
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; } |
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; } |
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
).
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; } |
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.
Deux autres fonctions peuvent être utiles pour la gestion de la mémoire :
void * calloc( size_t number, size_t byteSize ) : permet d'allouer un bloc de mémoire (un peu comme malloc
) mais en garantissant que celui-ci
est initialisé avec la valeur 0
. Consultez la page dédiée à cette fonction pour de plus amples explications.
void * realloc( void * pointer, size_t memorySize ) : permet de changer la taille d'une zone de mémoire déjà allouée. Attention, si la nouvelle taille est supérieure à la taille du bloc actuel et que à la suite de cette zone se trouve un autre bloc déjà alloué, alors le bloc sera déplacé et agrandit ailleurs en mémoire. Consultez la page dédiée à cette fonction pour de plus amples explications.
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 :