web-dev-qa-db-fra.com

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

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.

123
user484457

Comment exécuter Valgrind

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)

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

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 de mémoire et des erreurs

  • Utilisez www.cplusplus.com ! Il possède une excellente documentation sur les fonctions C/C++.
  • Conseils généraux en cas de fuite 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.
    • Ne remplacez pas un pointeur par un nouveau sauf si l'ancienne mémoire est libérée.
  • Conseils généraux pour les erreurs de mémoire:
    • Accédez et écrivez à des adresses et des index dont vous êtes sûr qu'ils vous appartiennent. Les erreurs de mémoire sont différentes des fuites. ils ne sont souvent que IndexOutOfBoundsException problèmes de type.
    • Ne pas accéder ou écrire en mémoire après l'avoir libéré.
  • Parfois, Valgrind ne pointe pas toujours exactement où se trouve votre erreur. Parfois, vous avez beaucoup d’erreurs et la résolution de l’une d’entre elles résout une cascade d’autres.
    • Énumérez les fonctions de votre code qui dépendent de/dépendent du code "fautif" contenant l'erreur de mémoire. Suivez l’exécution du programme (peut-être même dans gdb peut-être) et recherchez les erreurs de précondition/post-condition.
    • Essayez de commenter le bloc de code "incriminé" (dans des limites raisonnables, de sorte que votre code soit toujours compilé). Si l'erreur Valgrind disparaît, vous avez trouvé où elle se trouve.
  • Si tout échoue, essayez de regarder. Valgrind a documentation aussi!

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'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é.

Écriture invalide

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.

Lecture invalide

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.


L'Open Source (U/Dys) topia

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.

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

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.

SDL : une bibliothèque de graphismes 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 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!)

Réponses au vide

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!


Lectures complémentaires

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

178
Joshua Detwiler

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.

138
RageD

Tu peux courir:

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