Cours C 13 GESTION MEMOIRE
Support de cours C 13 gestion mémoire enjeux et pratique
…
n Parmi les erreurs les plus typiques d'un programme C (ou C++) liées aux allocations dynamiques, on peut citer :
n Les "fuites mémoires" (mémory leak)
n l'utilisation de la mémoire après sa libération
n la mémoire libérée plusieurs fois
n des débordements de tableaux
n l'utilisation de zones mémoire non initialisées.
n Une des particularités des problème mémoire
est la difficulté de trouver leur origine
n Des outils existent :
n Ils ne remplacent pas une bonne conception et
une implémentation soignée, il les complète.
n Quand on trouve une erreur, que fait-on ?
n Le pire est possible ...
n précipitation vs qualité !
n Trouver le bon niveau de correction : faute de frappe, erreur basique, erreur plus sophistiquée, problème de conception
Types d'outils
On peut citer des critères importants qui permettent de différencier les outils:
on doit modifier le code source pour pouvoir l'utiliser
on doit recompiler/relinker pour pouvoir l'utiliser
rien à faire
besoins primaires :
intercepter tous les appels à malloc/calloc/free/realloc
utiliser un gestionnaire mémoire plus sophistiqué
en modifiant l'exécutable ou les librairies
en modifiant le code source
par exemple:
#define malloc(n) mallocEx(n)
...
n Exemples d'utilisation de valgrind 3.0.1
n valgrind.org
n Valgrind regroupe un ensemble d'outils de debug et profiling.
n on s'intéresse ici à memcheck, l'outil de debug des problèmes de gestion mémoire.
Valgrind ne nécessite aucune modification des sources ni recompilation !
Valgrind utilise la table de symboles (option –g)
n Les exemples proviennent principalement du répertoire de tests de mpatrol
int main(void)
{
char *p = NULL;
if (p = (char *) malloc(8))
{
strcpy(p, "le monde");
free(p); p = NULL;
}
return EXIT_SUCCESS;
}
IR1 2007-2008 31/03/08 70
n Compilation & éditions de liens
gcc –g –ansi -Wall –c ex.c
gcc –g –o ex ex.o
n gdb
OK !
+ Bug actif mais invisible
+ Bug sans effet (à un instant t)
Extraits
==1673== Invalid write of size 1
==1673== at 0x80483E4: f (ex.c:17)
==1673== by 0x804841D: main (ex.c:25)
==1673== Address 0x1BA4D030 is 0 bytes after a block of size 8 alloc'd
==1673== at 0x1B8FF896: malloc (vg_ replace malloc.c:149)
_
==1673== by 0x80483C7: f (ex.c:15)
==1673== by 0x804841D: main (ex.c:25)
Table des symboles ?
n Compilation & éditions de liens
gcc –ansi -Wall –c ex.c
gcc –o ex ex.o
n valgrind
=1724= Invalid write of size 1
=1724= at 0x80483E4: f (in /home/MLV/TPs/ex)
=1724= by 0x804841D: main (in /home/MLV/TPs/ex)
=1724= Address 0x1BA4D030 is 0 bytes after a block of size
8 alloc'd
=1724= at 0x1B8FF896: malloc (vg_replace
_
=1724= by 0x80483C7: f (in /home/MLV/TPs/ex)
=1724= by 0x804841D: main (in /home/MLV/TPs/ex)
n Suppression des symboles
strip ex
n valgrind
=813== Invalid write of size 1
=813== at 0x80483E4: (within /home/MLV/TPs/ex)
=813== by 0x804841D: (within /home/MLV/TPs/ex)
=813== by 0x1B92EE3F: __libc_start
_ main (in /lib/tls/libc-2.3.5.so)
=813== by 0x8048330: (within /home/MLV/TPs/ex)
=813== Address 0x1BA4D030 is 0 bytes after a block of size 8 alloc'd
=813== at 0x1B8FF896: malloc (vg_replace
_ malloc.c:149)
=813== by 0x80483C7: (within /home/MLV/TPs/ex)
=813== by 0x804841D: (within /home/MLV/TPs/ex)
=813== by 0x1B92EE3F: __libc_start
_ main (in /lib/tls/libc-2.3.5.so)
=813== by 0x8048330: (within /home/MLV/TPs/ex)
$ valgrind –db-attach=yes ex
À chaque erreur, valgrind propose de lancer le debugger pour attacher le processus !
/*
*Allocates a block of 16 bytes and then attempts to free the
* memory returned at an offset of 1 byte into the block.
*/
int main(void)
{
char *p;
if (p = (char *) malloc(16))
free(p + 1);
return EXIT_SUCCESS;
(gdb) run
Starting program: /home/MLV/TPs/GESTION-MEMOIRE/ex1
Program received signal SIGSEGV, Segmentation fault.
0x400a3e8a in free () from /lib/libc.so.6
(gdb) bt
#0 0x400a3e8a in free () from /lib/libc.so.6
#1 0x400a3bf4 in free () from /lib/libc.so.6
#2 0x0804846c in main () at ex1.c:15
#3 0x4003f280 in __libc_start
_ main () from
/lib/libc.so.6
IR1 2007-2008 31/03/08 77
==4511== Memcheck, a.k.a. Valgrind, a memory error detector
==4511== Invalid free() / delete / delete[]
==4511== at 0x4002A885: free (vg_replace
_ malloc.c:231)
==4511== by 0x804846B: main (ex1.c:15)
==4511== by 0x4025A27F: __libc_start
_ main (in
/lib/libc-2.2.4.so)
==4511== by 0x8048360: (within /home/MLV/TPs/GESTION
MEMOIRE/ex1)
==4511== Address 0x411AC025 is 1 bytes inside a block of size
16 alloc'd
==4511== at 0x4002A563: malloc (vg_
==4511== by 0x804844F: main (ex1.c:14)
int main(void)
{
char *p;
if (p = (char *) malloc(16))
{
strcpy(p, "hello");
printf(p);
}
return EXIT_SUCCESS;
avec gdb ...
n rien n'est jamais signalé !
==4658== malloc/free: in use at exit: 16 bytes in 1 blocks.
==4658== malloc/free: 1 allocs, 0 frees, 16 bytes allocated.
==4658== For counts of detected errors, rerun with: -v
==4658== searching for pointers to 1 not-freed blocks.
==4658== checked 3606064 bytes.
==4658== 16 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4658== at 0x4002A563: malloc (vg_replace
_
==4658== by 0x804847F: main (ex1leak.c:11) malloc.c:153)
==4658== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
=4658== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex1leak)
==4658==
==4658== LEAK SUMMARY:
==4658== definitely lost: 16 bytes in 1 blocks.
==4658== possibly lost: 0 bytes in 0 blocks.
==4658== still reachable: 0 bytes in 0 blocks.
==4658== suppressed: 0 bytes in 0 blocks.
==4658== Reachable blocks (those to which a pointer was found) are not shown.
==4658== To see them, rerun with: --show-reachable=yes
/*
*Allocates a block of 16 bytes and then attempts to free the
* memory returned at an offset of 1 byte into the block.
*/
int main(void)
{
char *p;
if (p = (char *) malloc(16))
{
free(p);
p = (char *) realloc(p, 32);
}
return EXIT_SUCCESS;
avec gdb ...
n Plantage simple ...
==4666== Invalid free() / delete / delete[]
==4666== at 0x4002ABF4: realloc (vg_replace
_ malloc.c:310)
==4666== by 0x80484A9: main (ex2.c:18)
==4666== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4666== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex2)
==4666== Address 0x411AC024 is 0 bytes inside a block of size 16 free'd
==4666== at 0x4002A885: free (vg_replace
_ malloc.c:231)
==4666== by 0x8048499: main (ex2.c:17)
==4666== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4666== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex2)
/*
* Detecting use of free memory
* Allocates a block of 16 bytes and then immediately frees it. A
* NULL character is written into the middle of the freed memory.
*/
int main(void)
{
char *p;
if (p = (char *) malloc(16))
{
free(p);
p[8] = '\0';
}
return EXIT_SUCCESS;
n Exécution normale !
==4679== Invalid write of size 1
==4679== at 0x8048473: main (ex3.c:18)
==4679== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4679== by 0x8048360: (within /home/MLV/TPs/GESTION-MEMOIRE/ex3)
==4679== Address 0x411AC02C is 8 bytes inside a block of size 16 free'd
==4679== at 0x4002A885: free (vg_replace
_ malloc.c:231)
==4679== by 0x8048469: main (ex3.c:17)
==4679== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4679== by 0x8048360: (within /home/MLV/TPs/GESTION-MEMOIRE/ex3)
/*
* Using overflow buffers
*
* Allocates a block of 16 bytes and then copies a string of 16
* bytes into the block. However, the string is copied to 1 byte
* before the allocated block which writes before the start of the block
*/
int main(void) {
char *p;
if (p = (char *) malloc(16)) {
strcpy(p - 1, "this test fails!");
free(p);
}
return EXIT_SUCCESS;
avec gdb ...
n Plantage simple.
IR1 2007-2008 31/03/08 89
==4692== Invalid write of size 1
==4692== at 0x400224D2: strcpy (mac_replace_strmem.c:173)
==4692== by 0x80484A0: main (ex4.c:20)
==4692== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4692== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex4)
==4692== Address 0x411AC023 is 1 bytes before a block of size 16 alloc'd
==4692== at 0x4002A563: malloc (vg_replace
_ malloc.c:153)
==4692== by 0x804847F: main (ex4.c:18)
==4692== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4692== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex4)
/*
* Bad memory operations
* Allocates a block of 16 bytes and then attempts to zero the contents
of
* the block. However, a zero byte is also written 1 byte before and 1
* byte after the allocated block.
*/
int main(void) {
char *p;
if (p = (char *) malloc(16)) {
memset(p - 1, 0, 18);
free(p);
}
return EXIT_SUCCESS;
avec gdb ...
n Exécution normale
==4718== Invalid write of size 4
==4718== at 0x402C6789: memset (in /lib/libc-2.2.4.so)
==4718== by 0x4025A27F: __libc_start
_ main (in /lib/libc-2.2.4.so)
==4718== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex6)
==4718== Address 0x411AC023 is 1 bytes before a block of size 16 alloc'd
==4718== at 0x4002A563: malloc (vg_replace
_ malloc.c:153)
==4718== by 0x804847F: main (ex6.c:16)
==4718== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex6)
==4718== Invalid write of size 1
==4718== at 0x402C6790: memset (in /lib/libc-2.2.4.so)
==4718== by 0x8048390: (within /home/MLV/TPs/GESTION-MEMOIRE/ex6)
==4718== Address 0x411AC034 is 0 bytes after a block of size 16 alloc'd
==4718== at 0x4002A563: malloc (vg_replace
_ malloc.c:153)
==4718== by 0x804847F: main (ex6.c:16)
Débordements (3)
La magie a des limites ...
/* Checking memory accesses
Allocates a single byte of memory and then attempts to read the byte as a word, resulting in some uninitialised bytes being read.
*/
int main(void)
{
int *p;
int r;
if (p = (int *) calloc(1, 1)) {
r = p[0];
free(p);
}
return EXIT_SUCCESS;
IR1 2007-2008 31/03/08 94
n Purify (Rational / IBM) : le plus connu !
n DevPartner (compuWare) (ex-BoundChecker)
n Insure++ (ParaSoft)
n et en GPL :
n memprof,
n dmalloc,
n mpatrol,
n Boehm Collector,
...
les différents outils sont souvent complémentaires !
n Objectifs:
n Analyser les performances et localiser les fonctions consommant le plus de CPU
n déterminer la fréquence des appels et permettre une classification en fonction du contexte (à partir de quelle fonction la fonction étudiée a-t-elle été appelée ?).
Un outil de profiling ne remplace pas une bonne conception et une implémentation soignée, il les complète.
n Nous étudierons ici un outil assez simple et généraliste:
n gprof de GNU
n puis deux variantes à usage spécifique :
n time
n strace
n gprof est compatible avec gcc.
n Utilisation : gprof se déroule en trois phases:
ncompilation spéciale pour ajouter automatiquement au code des instructions de profiling.
n Il faut ajouter les options -pg à la compilation ET à l'édition de liens.
n exécution du programme : cela crée un fichier binaire de données, par défaut gmon.out
n exécuter gprof pour exploiter les données collectées à l'exécution et obtenir un rapport
$ gcc -ansi -Wall -c -pg test.c
$ gcc -pg -o test test.o
$ ./test ...
n Génère gmon.out dans le répertoire courant
$ gprof ./test
....
gprof : données obtenues
n $ gprof mon_executable
n (avec gmon.out présent dans le répertoire courant)
n Flat profile
n liste des fonctions avec le nombre d'appels et le temps total passé dedans, en incluant ou pas les fonctions appelées.
n Permet de repérer très rapidement les quelques fonctions prenant beaucoup de temps.
Temps
gprof : at prfile passé dans la sample counts as 01 second.
fonction
% cumulative self self total
0.00 9.14 0.00 48 0.00 0.00 cat2string
0.00 9.14 0.00 42 0.00 0.00 extract_ rep
0.00 9.14 0.00 21 0.00 0.00 add file
0.00 9.14 0.00 3 0.00 0.00 add_
n Beaucoup plus de précisions sur le contexte :
n Contributions par appelant / appelée
n Pour chaque fonction, on a :
n les fonctions appelantes (les lignes au-dessus de la ligne de référence commençant par [i])
n proportion du temps pris par la fonction et par ses "enfants" (l'appel des sous-fonctions)
n nombre d'appels de la fonction, en indiquant aussi les appels récursifs
n pour chaque appel d' "enfant", contribution de la fonction au nombre total d'appel de cet "enfant".
call graph
index % time self children called name
0.41 8.73 1/1 main [2]
[1] 100.0 041 8.73 1 compressall [1]
561 2.87 21/21 compress [3]
00 0.22 1/1 ation_
0 0.00 1/1 croncodage [7]
.87
21quand
.00 994/9336 2 [4]
total
--------------------
...
suppr_liste [25]
0.00 0.00 /1 compressall [1]
par main
[25] 0.0 0.00 0.00 1 +510 suppr_
liste [25]
510 suppr_ liste [25]
…
% time self children called name
0.41 8.73 1/1 main [2]
[1] 100.0 0.41 8.73 1 compressall [1]
5.61 2.87 21/21 compress [3]
0.22 1/1 creation_
0.00 1/1 creation_
--
index by function name
n Simple récapitulatif de la liste des fonctions avec leur numéro associé dans le rapport.
[14] add_dir [7] creation_codage [23] option
[13] add_file [20] creation_entete_code [15] simplify
[18] add_prec_dir [4] exposant2 [24] somme_caracteres
[19] add_word [12] extract_rep (compress.c) [16] strCopyPart
[8] ajout_maillon_caractere [21] getPathEnd [25] suppr_liste_and_export
[11] cat2string [22] initCmd [26] systemFiles
[3] compress [17] init_liste_caractere [27] testCreationArchive
[1] compressall [9] longueur_int [5] tri_croissant
[6] creation_arbre [10] nb_caracteres [28] writeCompresHeader
gprof : Statistiques ...
n les nombres d'appels sont comptabilisés => donc précis et répétables
n Les temps sont échantillonnés (défaut 10 ms), donc sujet à des erreur statistiques
n Importance d'avoir une exécution assez longue
n Possibilité de sommer des « run » ...
n Option -s ou --sum
n Commerciaux
n Quantify (rational => IBM) : Unix/Windows
n DevPartner (compuWare)
n L'outil le plus simple !
n donne le temps d'exécution d'une commande:
n le temps écoulé
n le temps CPU en mode utilisateur
n le temps CPU en mode noyau (exécution d'appels systèmes)
Ela version GNU rajoute, si possible, des informations sur l'utilisation de la mémoire (swap) et des I/O.
n Lancement :
$ time [options] cmd [args cmd]
$ time irtar -f test.iar -z -c .
13.54user 0.34system 0:27.75elapsed
$ time "--format=%S;%U" --output=allTimes
--append cmd ...
$ time -v irtar
infos aussi complètes que possible : CPU-Mémoire-I/O
n strace (truss sur d'autres Unix) trace uniquement les appels systèmes et les signaux.
n Lancement:
n strace [options] commande [arguments de la commande]
n On peut obtenir la liste exhaustive des appels systèmes avec les arguments d'appels et le code retour
n strace envoie le résultat sur la sortie erreur
execve("../xxx/bin/irtar", ["../xxx/bin/irtar", "-f", "../../../../tmp/kk.igz", "-z", "-c", "CORPUS/text-only/"], [/* 66 vars */]) = 0
uname({sys="Linux", node="PF-LINUX-5", ...}) = 0
brk(0) = 0x8050000
open("/etc/ld.so.preload", O RDONLY) = -1 ENOENT (No such file or directory)
_
open("/etc/ld.so.cache", O RDONLY) = 3
_
close(3) = 0
open("/lib/tls/libc.so.6", O RDONLY) = 3
_
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220O\1"..., 512) = 512
getcwd("/home/MLV/IRTAR/_PF_ ", 1024) = 21
chdir("../../../../tmp") = 0
% time seconds usecs/call calls errors syscall
59.41
0.022979 14 1657 read
31.30 0.012107 23 537 write
5.66 0.002191 46 48 1 open
1.24 0.000481 11 44 munmap
0.66 0.000257 5 48 lstat64
0.57 0.000221 5 43 mmap2
0.45 0.000174 4 47 close
0.35 0.000134 3 47 fstat64
0.09 0.000035 7 5 brk
0.08 0.000032 6 5
0.06 0.000022 6 4 chdir
0.05 0.000020 5 4 getdents64
0.03 0.000011 4 3 getcwd
0.02 0.000007 7 1 set
_thread_area
0.02 0.000006 3 2 fcntl64
0.01 0.000004 4 1 uname
100.00 0.038681 2496 1 total
Les performances
n les problématiques sont liées
n performances
n sécurisation (redondance, cryptage, ...)
n fiabilité
n simplicité
n Amélioration des performances / de la sécurité
n penser aux autres problèmes que peut générer la solution
n penser aux autres problèmes que peut résoudre la solution
IR1 2006-2007 31/03/08 114
Lecture gros fichier (x 200)
static void readByByte()
{
int i, nb;
int fd = open(strFileName, O RDONLY);
_
assert(fd >= 0);
for (i=0; i<sizeof(buf); ++i)
{
nb = read(fd, BUF(i), 1);
assert(nb == 1);
}
close(fd);
static void readAll()
{
int nb;
int fd = open(strFileName, O RDONLY);
_
assert(fd >= 0);
nb = read(fd, buf, sizeof(buf));
close(fd);
}
Lecture gros fichier (x 5)
static void freadByByte()
{
int i, nb;
FILE* f = fopen(strFileName, "r");
assert(f);
for (i=0; i<sizeof(buf); ++i)
{
nb = fread(BUF(i), 1, 1, f);
assert(nb == 1);
}
fclose(f);
Lecture gros fichier (x 1)
static void freadAll()
{
int nb;
FILE* f = fopen(strFileName, "r");
assert(f);
nb = fread(buf, sizeof(buf), 1, f);
assert(nb == 1);
fclose(f);
Parcours de la mémoire (x 1)
n Parcours naturel
for (y=0; y<HEIGHT; ++y)
{
for (x=0; x<WIDTH; ++x)
{
n += tab[y][x];
Parcours de la mémoire (x 40)
n Parcours non naturel
for (x=0; x<WIDTH; ++x)
{
for (y=0; y<HEIGHT; ++y)
{
n += tab[y][x];