Come posso usare valgrind per trovare perdite di memoria?


183

Come posso usare valgrind per trovare le perdite di memoria in un programma?

Per favore qualcuno mi aiuti e descriva i passaggi per eseguire la procedura?

Sto usando Ubuntu 10.04 e ho un programma a.c, per favore aiutatemi.


16
Usa valgrind per testare il tuo programma compilato , non il codice sorgente.
Tony,

6
La risposta data di seguito da @RageD è corretta, perché non la accetti?
Pratik Singhal,

1
Una perdita è causata da qualcosa che non riesci a fare, ad es. memoria allocata libera. Quindi Valgrind non può mostrarti "dov'è" la perdita - solo tu sai dove non è più richiesta la memoria allocata. Tuttavia, indicandoti quale allocazione non è libera () d, tracciando l'uso di quella memoria attraverso il tuo programma, dovresti essere in grado di determinare dove dovrebbe essere libera () d. Un errore comune è uscire da una funzione senza liberare memoria allocata.
MikeW,

Risposte:


299

Come eseguire Valgrind

Non per insultare l'OP, ma per coloro che arrivano a questa domanda e sono ancora nuovi su Linux , potrebbe essere necessario installare Valgrind sul proprio sistema.

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind è facilmente utilizzabile per il codice C / C ++, ma può anche essere utilizzato per altre lingue se configurato correttamente (vedere questo per Python).

Per eseguire Valgrind , passare l'eseguibile come argomento (insieme a tutti i parametri al programma).

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

Le bandiere sono, in breve:

  • --leak-check=full: "ogni singola perdita verrà mostrata in dettaglio"
  • --show-leak-kinds=all: Mostra tutti i tipi di perdita "definiti, indiretti, possibili, raggiungibili" nel rapporto "completo".
  • --track-origins=yes: Favorisce l'output utile rispetto alla velocità. Questo tiene traccia delle origini di valori non inizializzati, che potrebbero essere molto utili per errori di memoria. Considera di disattivare se Valgrind è inaccettabilmente lento.
  • --verbose: Può parlarti di comportamenti insoliti del tuo programma. Ripeti per maggiore verbosità.
  • --log-file: Scrivi in ​​un file. Utile quando l'uscita supera lo spazio terminale.

Infine, ti piacerebbe vedere un rapporto Valgrind simile al seguente:

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)

Ho una perdita, ma DOVE ?

Quindi, hai una perdita di memoria e Valgrind non sta dicendo nulla di significativo. Forse qualcosa del genere:

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)

Diamo un'occhiata anche al codice C che ho scritto:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

Bene, c'erano 5 byte persi. Come è successo? Il rapporto di errore dice solo maine malloc. In un programma più ampio, sarebbe davvero problematico cacciare. Ciò è dovuto alla compilazione dell'eseguibile . Possiamo effettivamente ottenere dettagli riga per riga su cosa è andato storto. Ricompila il tuo programma con un flag di debug (sto usando gccqui):

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

Ora con questa build di debug, Valgrind punta alla riga esatta di codice che alloca la memoria che è trapelata! (La formulazione è importante: potrebbe non essere esattamente dove si trova la tua perdita, ma cosa è trapelato. La traccia ti aiuta a trovare dove .)

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)

Tecniche per il debug di perdite ed errori di memoria

  • Utilizza www.cplusplus.com ! Ha un'ottima documentazione sulle funzioni C / C ++.
  • Consigli generali per perdite di memoria:
    • Assicurati che la memoria allocata dinamicamente venga effettivamente liberata.
    • Non allocare memoria e dimenticare di assegnare il puntatore.
    • Non sovrascrivere un puntatore con uno nuovo a meno che la vecchia memoria non venga liberata.
  • Consigli generali per errori di memoria:
    • Accedi e scrivi a indirizzi e indici di cui sei sicuro. Gli errori di memoria sono diversi dalle perdite; spesso sono solo IndexOutOfBoundsException problemi di tipo.
    • Non accedere o scrivere in memoria dopo averlo liberato.
  • A volte le tue perdite / errori possono essere collegati tra loro, proprio come un IDE scoprendo che non hai ancora digitato una parentesi quadra di chiusura. Risolvere un problema può risolverne altri, quindi cercane uno che sembra un buon colpevole e applica alcune di queste idee:

    • Elencare le funzioni nel codice che dipendono / dipendono dal codice "offensivo" che presenta l'errore di memoria. Segui l'esecuzione del programma (forse anche gdbforse) e cerca gli errori di precondizione / postcondizione. L'idea è quella di tracciare l'esecuzione del programma concentrandosi sulla durata della memoria allocata.
    • Prova a commentare il blocco di codice "offensivo" (entro limiti ragionevoli, quindi il tuo codice viene ancora compilato). Se l'errore Valgrind scompare, hai trovato dove si trova.
  • Se tutto il resto fallisce, prova a cercarlo. Anche Valgrind ha documentazione !

Uno sguardo a perdite ed errori comuni

Guarda i tuoi puntatori

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)

E il codice:

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

Come assistente di insegnamento, ho visto spesso questo errore. Lo studente utilizza una variabile locale e si dimentica di aggiornare il puntatore originale. L'errore qui sta notando che reallocpuò effettivamente spostare la memoria allocata da qualche altra parte e cambiare la posizione del puntatore. Partiamo quindi resizeArraysenza dire array->datadove è stato spostato l'array.

Scrittura non valida

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)

E il codice:

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

Nota che Valgrind ci indica la riga di codice commentata sopra. La matrice di dimensioni 26 è indicizzata [0,25], motivo per cui si *(alphabet + 26)tratta di una scrittura non valida: è fuori limite. Una scrittura non valida è il risultato comune di errori off-by-one. Guarda il lato sinistro dell'operazione di assegnazione.

Lettura non valida

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)

E il codice:

#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 ci indica la riga commentata sopra. Guarda l'ultima iterazione qui, che è
*(destination + 26) = *(source + 26);. Tuttavia, *(source + 26)è di nuovo fuori limite, analogamente alla scrittura non valida. Letture non valide sono anche il risultato comune di errori off-by-one. Guarda il lato destro dell'operazione di assegnazione.


La topia Open Source (U / Dys)

Come faccio a sapere quando la perdita è mia? Come posso trovare la mia perdita quando sto usando il codice di qualcun altro? Ho trovato una perdita che non è mia; dovrei fare qualcosa? Tutte sono domande legittime. Innanzitutto, 2 esempi reali che mostrano 2 classi di incontri comuni.

Jansson : una libreria 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;
}

Questo è un programma semplice: legge una stringa JSON e la analizza. Nel processo, utilizziamo le chiamate in libreria per eseguire l'analisi per noi. Jansson effettua le allocazioni necessarie in modo dinamico poiché JSON può contenere strutture nidificate di se stesso. Tuttavia, ciò non significa che noi decref"liberiamo" la memoria che ci viene data da ogni funzione. In effetti, questo codice che ho scritto sopra genera sia una "lettura non valida" che una "scrittura non valida". Questi errori scompaiono quando togli la decreflinea per value.

Perché? La variabile valueè considerata un "riferimento preso in prestito" nell'API Jansson. Jansson tiene traccia della sua memoria per te e devi semplicemente disporre di decref strutture JSON indipendenti l'una dall'altra. La lezione qui: leggi la documentazione . Veramente. A volte è difficile da capire, ma ti stanno dicendo perché accadono queste cose. Invece, abbiamo domande esistenti su questo errore di memoria.

SDL : una libreria grafica e di gioco

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

Cosa c'è che non va in questo codice ? Perdono costantemente ~ 212 KiB di memoria per me. Prenditi un momento per pensarci. Attivare e disattivare SDL. Risposta? Non c'è niente di sbagliato.

All'inizio potrebbe sembrare strano . A dire il vero, la grafica è disordinata e a volte devi accettare alcune perdite come parte della libreria standard. La lezione qui: non è necessario reprimere ogni perdita di memoria . A volte devi solo eliminare le perdite perché sono noti problemi di cui non puoi fare nulla . (Questo non è il mio permesso di ignorare le tue perdite!)

Risposte al vuoto

Come faccio a sapere quando la perdita è mia?
È. (Sicuro al 99%)

Come posso trovare la mia perdita quando sto usando il codice di qualcun altro?
È probabile che qualcun altro lo abbia già trovato. Prova Google! Se fallisce, usa le abilità che ti ho dato sopra. Se il problema persiste e vengono visualizzate principalmente chiamate API e poca traccia dello stack, vedere la domanda successiva.

Ho trovato una perdita che non è mia; dovrei fare qualcosa?
Sì! La maggior parte delle API ha modi per segnalare bug e problemi. Usali! Aiuta a restituire gli strumenti che stai utilizzando nel tuo progetto!


Ulteriori letture

Grazie per stare con me così a lungo. Spero che tu abbia imparato qualcosa, mentre cercavo di tendere all'ampio spettro di persone che arrivavano a questa risposta. Spero che tu abbia chiesto alcune cose lungo la strada: come funziona l'allocatore di memoria di C? Che cos'è effettivamente una perdita di memoria e un errore di memoria? In che modo differiscono dai segfault? Come funziona Valgrind? Se hai avuto uno di questi, ti preghiamo di alimentare la tua curiosità:


4
Risposta molto migliore, peccato che questa non sia la risposta accettata.
A. Smoliak,

Credo che sia una buona pratica fare una cosa del genere, ne ho fatti un po 'me stesso
A. Smoliak,

1
Posso aggiungere questa risposta a Speciali e usarla come riferimento futuro per me stesso? Buon lavoro!
Zap,

lo memcheckstrumento è abilitato di default?
abhiarora,

@abhiarora Sì. La pagina man ci dice che memcheckè lo strumento predefinito:--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

Prova questo:

valgrind --leak-check=full -v ./your_program

Finché valgrind è installato, passerà attraverso il tuo programma e ti dirà cosa c'è che non va. Può darti puntatori e luoghi approssimativi in ​​cui si possono trovare le perdite. Se stai segfault, prova a eseguirlo gdb.


Cosa significa "tuo_programma"? Questa posizione del codice sorgente o il nome dell'applicazione, come il file apk?
HoangVu,

7
your_program== il nome dell'eseguibile o qualunque comando tu usi per eseguire la tua applicazione.
Rabbia,

27

Puoi eseguire:

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

1

È possibile creare un alias nel file .bashrc come segue

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

Quindi, quando vuoi controllare le perdite di memoria, fai semplicemente

vg ./<name of your executable> <command line parameters to your executable>

Ciò genererà un file di registro Valgrind nella directory corrente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.