Support de cours C 13 gestion memoire enjeux et pratique


Télécharger Support de cours C 13 gestion memoire enjeux et pratique
3.53.5 étoiles sur 5 a partir de 1 votes.
Votez ce document:

Télécharger aussi :


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 d'1 gros fichier (50Mo)
  • Appel système read 1 byte / call
  • Appel système read 50Mo / call
  • Lib C standard : fread 1 byte / call
  • Lib C standard : fread 50Mo / call

 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];


695