Comment utiliser valgrind pour rechercher les fuites de mémoire dans un programme?
S'il vous plaît quelqu'un m'aider et décrire les étapes pour mener à bien la procédure?
J'utilise Ubuntu 10.04 et j'ai un programme a.c
, aidez-moi s'il vous plaît.
Je voudrais élaborer une explication plus détaillée de l’utilisation efficace de Valgrind et de la résolution des fuites de mémoire. Ne pas insulter l'OP, mais pour ceux qui viennent à cette question et qui sont encore nouveaux pour Linux - , vous devrez peut-être installer Valgrind sur votre système.
Sudo apt install valgrind # Ubuntu, Debian, etc.
Sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
Valgrind est facilement utilisable pour le code C/C++, mais peut également être utilisé pour d’autres langages correctement configuré (voir this pour Python).
Pour exécuter valgrind , transmettez l'exécutable en tant qu'argument (avec tous les paramètres du programme).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
Cela produira un rapport à la fin de l'exécution de votre programme qui (espérons-le) ressemble à ceci:
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Donc, vous avez une fuite de mémoire, et Valgrind ne dit rien de significatif. Peut-être, quelque chose comme ça:
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (in /home/Peri461/Documents/executable)
Jetons un coup d'œil au code C que j'ai écrit aussi:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Eh bien, il y avait 5 octets perdus. Comment est-ce arrivé? Le rapport d'erreur indique simplement main
et malloc
. Dans un programme plus vaste, il serait très difficile de traquer. Cela est dû à la manière dont l'exécutable a été compilé . Nous pouvons réellement obtenir des détails ligne par ligne sur ce qui ne va pas. Recompilez votre programme avec un drapeau de débogage (j'utilise gcc
ici):
gcc -o executable -std=c11 -Wall main.c # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c # add -ggdb3 to it
Maintenant, avec cette version de débogage, , Valgrind pointe la ligne de code exacte allouant la mémoire qui a été perdue! (Le libellé est important: il se peut que ce ne soit pas exactement le lieu de votre fuite, mais ce que a fui. La trace vous aide à trouver où .
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (main.c:4)
IndexOutOfBoundsException
problèmes de type.gdb
peut-être) et recherchez les erreurs de précondition/post-condition.60 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
by 0x4005E4: resizeArray (main.c:12)
by 0x40062E: main (main.c:19)
Et le code:
#include <stdlib.h>
#include <stdint.h>
struct _List {
int32_t* data;
int32_t length;
};
typedef struct _List List;
List* resizeArray(List* array) {
int32_t* dPtr = array->data;
dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
return array;
}
int main() {
List* array = calloc(1, sizeof(List));
array->data = calloc(10, sizeof(int32_t));
array = resizeArray(array);
free(array->data);
free(array);
return 0;
}
En tant qu'assistante pédagogique, j'ai souvent vu cette erreur. L'élève utilise une variable locale et oublie de mettre à jour le pointeur d'origine. L'erreur ici est de constater que realloc
peut réellement déplacer la mémoire allouée ailleurs et changer l'emplacement du pointeur. Nous laissons ensuite resizeArray
sans indiquer à array->data
où le tableau a été déplacé.
1 errors in context 1 of 1:
Invalid write of size 1
at 0x4005CA: main (main.c:10)
Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
at 0x4C2B975: calloc (vg_replace_malloc.c:711)
by 0x400593: main (main.c:5)
Et le code:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* alphabet = calloc(26, sizeof(char));
for(uint8_t i = 0; i < 26; i++) {
*(alphabet + i) = 'A' + i;
}
*(alphabet + 26) = '\0'; //null-terminate the string?
free(alphabet);
return 0;
}
Notez que Valgrind nous indique la ligne de code commentée ci-dessus. Le tableau de taille 26 est indexé [0,25], raison pour laquelle *(alphabet + 26)
est une écriture non valide. Elle est hors limites. Une écriture invalide est un résultat courant d'erreurs différentes. Regardez le côté gauche de votre opération d'affectation.
1 errors in context 1 of 1:
Invalid read of size 1
at 0x400602: main (main.c:9)
Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x4005E1: main (main.c:6)
Et le code:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* destination = calloc(27, sizeof(char));
char* source = malloc(26 * sizeof(char));
for(uint8_t i = 0; i < 27; i++) {
*(destination + i) = *(source + i); //Look at the last iteration.
}
free(destination);
free(source);
return 0;
}
Valgrind nous indique la ligne commentée ci-dessus. Regardez la dernière itération ici, qui est*(destination + 26) = *(source + 26);
. Cependant, *(source + 26)
est à nouveau hors limites, de la même manière que l'écriture invalide. Les lectures non valides sont également le résultat fréquent d'erreurs uniques. Regardez le côté droit de votre opération d'affectation.
Comment savoir quand la fuite est la mienne? Comment trouver ma fuite lorsque j'utilise le code de quelqu'un d'autre? J'ai trouvé une fuite qui n'est pas la mienne; devrais-je faire quelque chose? Toutes sont des questions légitimes. Premièrement, deux exemples réels illustrant deux classes de rencontres courantes.
#include <jansson.h>
#include <stdio.h>
int main() {
char* string = "{ \"key\": \"value\" }";
json_error_t error;
json_t* root = json_loads(string, 0, &error); //obtaining a pointer
json_t* value = json_object_get(root, "key"); //obtaining a pointer
printf("\"%s\" is the value field.\n", json_string_value(value)); //use value
json_decref(value); //Do I free this pointer?
json_decref(root); //What about this one? Does the order matter?
return 0;
}
Ceci est un programme simple: il lit une chaîne JSON et l’analyse. En cours de réalisation, nous utilisons des appels de bibliothèque pour effectuer l'analyse syntaxique pour nous. Jansson effectue les allocations nécessaires de manière dynamique, car JSON peut contenir des structures imbriquées. Cependant, cela ne signifie pas que nous decref
ou "libérons" la mémoire qui nous est donnée de chaque fonction. En fait, le code que j'ai écrit ci-dessus jette à la fois une "lecture invalide" et une "écriture invalide". Ces erreurs disparaissent lorsque vous supprimez la ligne decref
pour value
.
Pourquoi? La variable value
est considérée comme une "référence empruntée" dans l'API Jansson. Jansson garde la trace de sa mémoire pour vous et vous devez simplement decref
structures JSON indépendantes les unes des autres. La leçon ici: lisez la documentation . Vraiment. C'est parfois difficile à comprendre, mais ils vous expliquent pourquoi cela se produit. Au lieu de cela, nous avons questions existantes à propos de cette erreur de mémoire.
#include "SDL2/SDL.h"
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Quit();
return 0;
}
Quel est le problème avec ce code ? Il me manque toujours environ 212 Ko de mémoire. Prenez un moment pour y réfléchir. Nous activons puis désactivons SDL. Répondre? Il n'y a rien de mal.
Cela peut paraître bizarre au début . À vrai dire, les graphiques sont en désordre et vous devez parfois accepter certaines fuites comme faisant partie de la bibliothèque standard. La leçon ici: vous n'avez pas besoin de calmer chaque fuite de mémoire . Parfois, vous avez juste besoin de supprimer les fuites car ce sont des problèmes connus sur lesquels vous ne pouvez rien faire . (Ce n'est pas ma permission d'ignorer vos propres fuites!)
Comment savoir quand la fuite est la mienne?
Il est. (99% de toute façon)
Comment trouver ma fuite lorsque j'utilise le code de quelqu'un d'autre?
Il est fort probable que quelqu'un d'autre l'a déjà trouvé. Essayez Google! Si cela échoue, utilisez les compétences que je vous ai données ci-dessus. Si cela échoue et que vous voyez principalement des appels d'API et très peu de votre propre trace de pile, reportez-vous à la question suivante.
J'ai trouvé une fuite qui n'est pas la mienne; devrais-je faire quelque chose?
Oui! La plupart des API ont des moyens de signaler des bogues et des problèmes. Utilise les! Aidez à redonner aux outils que vous utilisez dans votre projet!
Merci de rester avec moi aussi longtemps. J'espère que vous avez appris quelque chose, car j'ai essayé de tenir compte du large éventail de personnes qui parviennent à cette réponse. J'espère que vous avez posé des questions au cours du processus: comment l’allocateur de mémoire de C fonctionne-t-il? Qu'est-ce qu'une fuite de mémoire et une erreur de mémoire? Comment sont-ils différents des segfaults? Comment fonctionne Valgrind? Si vous en avez une, alimentez votre curiosité:
Essaye ça:
valgrind --leak-check=full -v ./your_program
Tant que valgrind est installé, il va parcourir votre programme et vous dire ce qui ne va pas. Il peut vous donner des indications et des endroits approximatifs où vos fuites peuvent être trouvées. Si vous commettez une erreur de segmentation, essayez de l'exécuter avec gdb
.
Tu peux courir:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]