Comment utiliser valgrind pour trouver 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.
Comment utiliser valgrind pour trouver 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.
Réponses:
Pas pour insulter l'OP, mais pour ceux qui viennent à cette question et sont encore nouveaux sur 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 même être utilisé pour d'autres langages lorsqu'il est configuré correctement (voir ceci pour Python).
Pour exécuter Valgrind , passez l'exécutable en argument (avec tous les paramètres au programme).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
Les drapeaux sont, en bref:
--leak-check=full
: "chaque fuite individuelle sera montrée en détail"--show-leak-kinds=all
: Affiche tous les types de fuites "définies, indirectes, possibles, atteignables" dans le rapport "complet".--track-origins=yes
: Préférez la sortie utile à la vitesse. Cela permet de suivre les origines des valeurs non initialisées, ce qui peut être très utile pour les erreurs de mémoire. Pensez à l'éteindre si Valgrind est trop lent.--verbose
: Peut vous informer du comportement inhabituel de votre programme. Répétez pour plus de verbosité.--log-file
: Ecrire dans un fichier. Utile lorsque la sortie dépasse l'espace du terminal.Enfin, vous aimeriez voir un rapport Valgrind qui 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 a eu 5 octets perdus. Comment est-ce arrivé? Le rapport d'erreur dit simplement
main
et malloc
. Dans un programme plus vaste, ce serait très difficile à traquer. Cela est dû à la manière dont l'exécutable a été compilé . Nous pouvons en fait obtenir des détails ligne par ligne sur ce qui n'a pas fonctionné. Recompilez votre programme avec un indicateur 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 vers la ligne exacte de code allouant la mémoire qui a été divulguée! (Le libellé est important: ce n'est peut-être pas exactement où se trouve votre fuite, mais ce qui a été divulgué. 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
des problèmes de type.Parfois, vos fuites / erreurs peuvent être liées les unes aux autres, un peu comme un IDE découvrant que vous n'avez pas encore tapé de parenthèse fermante. La résolution d'un problème peut en résoudre d'autres, alors recherchez-en un qui semble être un bon coupable et appliquez certaines de ces idées:
gdb
être) et recherchez les erreurs de précondition / postcondition. L'idée est de suivre l'exécution de votre programme tout en se concentrant sur la durée de vie de la mémoire allouée.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'assistant d'enseignement, 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 remarquer que realloc
peut en fait déplacer la mémoire allouée ailleurs et changer l'emplacement du pointeur. Nous partons ensuite resizeArray
sans dire
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], c'est pourquoi il *(alphabet + 26)
s'agit d'une écriture invalide - elle est hors limites. Une écriture non valide est un résultat courant d'erreurs hors par un. 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 un résultat courant d'erreurs off-by-one. Regardez le côté droit de votre opération d'affectation.
Comment savoir si la fuite est à moi? 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; dois-je faire quelque chose? Ce sont toutes des questions légitimes. Tout d'abord, 2 exemples du monde réel qui montrent 2 classes de rencontres communes.
#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;
}
C'est un programme simple: il lit une chaîne JSON et l'analyse. Dans la fabrication, nous utilisons des appels de bibliothèque pour faire l'analyse pour nous. Jansson effectue les allocations nécessaires de manière dynamique car JSON peut contenir des structures imbriquées de lui-même. Cependant, cela ne signifie pas que nous decref
ou «libérons» la mémoire qui nous est donnée de chaque fonction. En fait, ce code que j'ai écrit ci-dessus lance à la fois une "lecture invalide" et une "écriture invalide". Ces erreurs disparaissent lorsque vous supprimez la decref
ligne pour value
.
Pourquoi? La variable value
est considérée comme une "référence empruntée" dans l'API Jansson. Jansson garde une trace de sa mémoire pour vous, et vous avez simplement decref
des 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 ces choses se produisent. Au lieu de cela, nous avons
des questions existantes sur 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 fuit constamment ~ 212 Kio de mémoire pour moi. 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 sembler bizarre au début . À vrai dire, les graphiques sont désordonnés et parfois vous devez accepter certaines fuites comme faisant partie de la bibliothèque standard. La leçon ici: vous n'avez pas besoin d'étouffer toutes les fuites de mémoire . Parfois, vous devez simplement supprimer les fuites, car ce sont des problèmes connus pour lesquels vous ne pouvez rien faire . (Ce n'est pas ma permission d'ignorer vos propres fuites!)
Comment savoir si la fuite est la mienne?
C'est. (99% sûr, de toute façon)
Comment trouver ma fuite lorsque j'utilise le code de quelqu'un d'autre?
Il y a de fortes chances que quelqu'un d'autre l'ait 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 peu de votre propre trace de pile, consultez la question suivante.
J'ai trouvé une fuite qui n'est pas la mienne; dois-je faire quelque chose?
Oui! La plupart des API ont des moyens de signaler les bogues et les problèmes. Utilise les! Aidez à redonner aux outils que vous utilisez dans votre projet!
Merci d'être resté avec moi aussi longtemps. J'espère que vous avez appris quelque chose, en essayant de m'occuper du large éventail de personnes qui arrivent à cette réponse. J'espère que vous avez demandé certaines choses en cours de route: Comment fonctionne l'allocateur de mémoire de C? Qu'est-ce qu'une fuite de mémoire et une erreur de mémoire? En quoi sont-ils différents des segfaults? Comment fonctionne Valgrind? Si vous en aviez, nourrissez votre curiosité:
memcheck
outil est-il activé par défaut?
memcheck
c'est l'outil par défaut:--tool=<toolname> [default: memcheck]
Essaye ça:
valgrind --leak-check=full -v ./your_program
Tant que valgrind est installé, il passera par votre programme et vous dira ce qui ne va pas. Il peut vous donner des indications et des endroits approximatifs où vos fuites peuvent être trouvées. Si vous faites une erreur de segmentation, essayez de l'exécuter gdb
.
your_program
== le nom de l'exécutable ou la commande que vous utilisez pour exécuter votre application.
Tu peux courir:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Vous pouvez créer un alias dans le fichier .bashrc comme suit
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
Donc, chaque fois que vous voulez vérifier les fuites de mémoire, faites simplement
vg ./<name of your executable> <command line parameters to your executable>
Cela générera un fichier journal Valgrind dans le répertoire actuel.