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.
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.
Risposte:
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)
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
main
e 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 gcc
qui):
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)
IndexOutOfBoundsException
problemi di tipo.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:
gdb
forse) e cerca gli errori di precondizione / postcondizione. L'idea è quella di tracciare l'esecuzione del programma concentrandosi sulla durata della memoria allocata.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 realloc
può effettivamente spostare la memoria allocata da qualche altra parte e cambiare la posizione del puntatore. Partiamo quindi resizeArray
senza dire
array->data
dove è stato spostato l'array.
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.
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.
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.
#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 decref
linea 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.
#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!)
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!
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à:
memcheck
strumento è abilitato di default?
memcheck
è lo strumento predefinito:--tool=<toolname> [default: memcheck]
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
.
your_program
== il nome dell'eseguibile o qualunque comando tu usi per eseguire la tua applicazione.
Puoi eseguire:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
È 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.