Comment utiliser valgrind pour trouver des fuites de mémoire?


183

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.


16
Vous utilisez valgrind pour tester votre programme compilé , pas le code source.
Tony

6
La réponse donnée ci-dessous par @RageD est correcte, pourquoi ne l'acceptez-vous pas?
Pratik Singhal

1
Une fuite est causée par quelque chose que vous ne faites pas - c.-à-d. mémoire allouée libre. Par conséquent, Valgrind ne peut pas vous montrer «où» se trouve la fuite - vous seul savez où la mémoire allouée n'est plus nécessaire. Cependant, en vous indiquant quelle allocation n'est pas libre () d, en traçant l'utilisation de cette mémoire à travers votre programme, vous devriez être en mesure de déterminer où elle devrait obtenir free () d. Une erreur courante consiste à quitter une fonction par erreur sans libérer la mémoire allouée.
MikeW

Réponses:


299

Comment exécuter Valgrind

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)

J'ai une fuite, mais ?

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 mainet 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 gccici):

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 .)

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)

Techniques de débogage des fuites et des erreurs de mémoire

  • Utilisez www.cplusplus.com ! Il a une excellente documentation sur les fonctions C / C ++.
  • Conseils généraux pour les fuites de mémoire:
    • Assurez-vous que votre mémoire allouée dynamiquement est effectivement libérée.
    • N'allouez pas de mémoire et oubliez d'attribuer le pointeur.
    • N'écrasez pas un pointeur par un nouveau à moins que l'ancienne mémoire ne soit libérée.
  • Conseils généraux pour les erreurs de mémoire:
    • Accédez et écrivez aux adresses et aux index dont vous êtes sûr de vous appartenir. Les erreurs de mémoire sont différentes des fuites; ce ne sont souvent que IndexOutOfBoundsException des problèmes de type.
    • N'accédez pas ou n'écrivez pas dans la mémoire après l'avoir libérée.
  • 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:

    • Énumérez les fonctions de votre code qui dépendent / dépendent du code «offensant» qui a l'erreur de mémoire. Suivez l'exécution du programme (peut-être même peut- 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.
    • Essayez de commenter le bloc de code "offensant" (dans des limites raisonnables, afin que votre code se compile toujours). Si l'erreur Valgrind disparaît, vous avez trouvé où elle se trouve.
  • Si tout le reste échoue, essayez de le rechercher. Valgrind a aussi de la documentation !

Un regard sur les fuites et les erreurs courantes

Surveillez vos pointeurs

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 reallocpeut en fait déplacer la mémoire allouée ailleurs et changer l'emplacement du pointeur. Nous partons ensuite resizeArraysans dire array->dataoù le tableau a été déplacé.

Écriture non valide

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.

Lecture non valide

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.


Les topia Open Source (U / Dys)

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.

Jansson : une bibliothèque JSON

#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 decrefou «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 decrefligne pour value.

Pourquoi? La variable valueest 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.

SDL : une bibliothèque graphique et de jeux

#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!)

Réponses au vide

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!


Lectures complémentaires

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é:


4
Bien meilleure réponse, dommage que ce ne soit pas la réponse acceptée.
A. Smoliak

Je crois que c'est une bonne pratique de faire une telle chose, j'en ai fait quelques-unes moi
A. Smoliak

1
Puis-je marquer cette réponse et l'utiliser comme référence future pour moi-même? Bon travail!
Zap

l' memcheckoutil est-il activé par défaut?
abhiarora le

@abhiarora Oui. La page de manuel nous indique que memcheckc'est l'outil par défaut:--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

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.


Que signifie "votre_programme"? Cet emplacement du code source ou ce nom d'application est-il tel qu'un fichier apk?
HoangVu

7
your_program== le nom de l'exécutable ou la commande que vous utilisez pour exécuter votre application.
RageD

27

Tu peux courir:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]

1

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.