Perdita ancora raggiungibile rilevata da Valgrind


154

Tutte le funzioni menzionate in questo blocco sono funzioni di libreria. Come posso correggere questa perdita di memoria?

È elencato nella categoria " Ancora raggiungibile ". (Ce ne sono altri 4, che sono molto simili, ma di varie dimensioni)

 630 bytes in 1 blocks are still reachable in loss record 5 of 5
    at 0x4004F1B: calloc (vg_replace_malloc.c:418)
    by 0x931CD2: _dl_new_object (dl-object.c:52)
    by 0x92DD36: _dl_map_object_from_fd (dl-load.c:972)
    by 0x92EFB6: _dl_map_object (dl-load.c:2251)
    by 0x939F1B: dl_open_worker (dl-open.c:255)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0x9399C5: _dl_open (dl-open.c:584)
    by 0xA64E31: do_dlopen (dl-libc.c:86)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0xA64FF4: __libc_dlopen_mode (dl-libc.c:47)
    by 0xAE6086: pthread_cancel_init (unwind-forcedunwind.c:53)
    by 0xAE61FC: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)

Cattura: una volta eseguito il mio programma, non ha prodotto perdite di memoria, ma aveva una riga aggiuntiva nell'output di Valgrind, che prima non era presente:

Scartare le palestre su 0x5296fa0-0x52af438 in /lib/libgcc_s-4.4.4-20100630.so.1 a causa di munmap ()

Se la perdita non può essere corretta, qualcuno potrebbe spiegare perché la linea munmap () fa sì che Valgrind segnali 0 perdite "ancora raggiungibili"?

Modificare:

Ecco un esempio di test minimo:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *runner(void *param) {
    /* some operations ... */
    pthread_exit(NULL);
}

int n;

int main(void) {

    int i;
    pthread_t *threadIdArray;

    n=10; /* for example */

    threadIdArray = malloc((n+n-1)*sizeof(pthread_t));  

    for(i=0;i<(n+n-1);i++) {
        if( pthread_create(&threadIdArray[i],NULL,runner,NULL) != 0 ) {
            printf("Couldn't create thread %d\n",i);
            exit(1);
        }
    }


    for(i=0;i<(n+n-1);i++) {
        pthread_join(threadIdArray[i],NULL);
    }

    free(threadIdArray);

    return(0);
}

Corri con:

valgrind -v --leak-check=full --show-reachable=yes ./a.out

Risposte:


378

Esiste più di un modo per definire "perdita di memoria". In particolare, ci sono due definizioni principali di "perdita di memoria" che sono di uso comune tra i programmatori.

La prima definizione comunemente usata di "perdita di memoria" è "La memoria è stata allocata e non è stata successivamente liberata prima della fine del programma". Tuttavia, molti programmatori (giustamente) sostengono che alcuni tipi di perdite di memoria che si adattano a questa definizione in realtà non presentano alcun tipo di problema e quindi non dovrebbero essere considerati vere e proprie "perdite di memoria".

Una definizione discutibilmente più rigorosa (e più utile) di "perdita di memoria" è "La memoria è stata allocata e non può essere successivamente liberata perché il programma non ha più alcun puntatore al blocco di memoria allocato". In altre parole, non è possibile liberare memoria a cui non si ha più alcun puntatore. Tale memoria è quindi una "perdita di memoria". Valgrind utilizza questa definizione più rigorosa del termine "perdita di memoria". Questo è il tipo di perdita che può potenzialmente causare un significativo esaurimento dell'heap, specialmente per processi di lunga durata.

La categoria "ancora raggiungibile" nel rapporto sulle perdite di Valgrind si riferisce alle allocazioni che si adattano solo alla prima definizione di "perdita di memoria". Questi blocchi non sono stati liberati, ma avrebbero potuto essere liberati (se il programmatore lo avesse voluto) perché il programma stava ancora tenendo traccia dei puntatori a quei blocchi di memoria.

In generale, non è necessario preoccuparsi di blocchi "ancora raggiungibili". Non rappresentano il tipo di problema che possono causare vere perdite di memoria. Ad esempio, normalmente non esiste alcun potenziale di esaurimento dell'heap da blocchi "ancora raggiungibili". Questo perché questi blocchi sono di solito allocazioni singole, riferimenti a cui vengono conservati per tutta la durata del processo. Sebbene sia possibile eseguire e assicurarsi che il programma liberi tutta la memoria allocata, di solito non vi è alcun vantaggio pratico nel farlo poiché il sistema operativo recupererà tutta la memoria del processo dopo che il processo è terminato, comunque. Contrasta questo con vero perdite di memoria che, se lasciate non fissate, potrebbero causare l'esaurimento della memoria di un processo se lasciate in esecuzione abbastanza a lungo o semplicemente far sì che un processo consumi molta più memoria del necessario.

Probabilmente l'unica volta che è utile assicurarsi che tutte le allocazioni abbiano "liberazioni" corrispondenti è se i tuoi strumenti di rilevamento delle perdite non sono in grado di dire quali blocchi sono "ancora raggiungibili" (ma Valgrind può farlo) o se il tuo sistema operativo non recupera tutto la memoria di un processo finale (tutte le piattaforme su cui Valgrind è stato portato per farlo).


puoi ipotizzare cosa sta facendo la munmap () che fa sparire i blocchi "ancora raggiungibili"?

3
@crypto: potrebbe essere munmapinvocato a seguito dello scarico di un oggetto condiviso. E tutte le risorse utilizzate dall'oggetto condiviso potrebbero essere liberate prima che venga scaricato. Questo potrebbe spiegare perché i "ancora raggiungibili" vengano liberati nel munmapcaso. Sto solo speculando qui, però. Non ci sono abbastanza informazioni qui per dirlo con certezza.
Dan Molding

3
Un caso in cui la memoria "ancora raggiungibile" può essere considerata una perdita di memoria: si supponga di avere una tabella hash in cui si aggiungono i puntatori alla memoria allocata dell'heap come valore. Se continui a inserire nuove voci nella tabella, ma non rimuoverai e libererai quelli che non ti servono più, può crescere indefinitamente, perdendo un evento di memoria heap se quella memoria è tecnicamente "ancora raggiungibile". Questo è il caso della perdita di memoria che puoi avere in Java o in altri linguaggi di immondizia raccolti.
lvella

Vedi anche questa risposta nelle FAQ di valgrind sui blocchi "ancora raggiungibili" creati da STL. valgrind.org/docs/manual/faq.html#faq.reports
John Perry

5
"molti programmatori (giustamente) sostengono che [la memoria trapelata] in realtà non pone [un] problema, e quindi non dovrebbe essere considerata una vera perdita di memoria" - Lol ... Crea una DLL nativa con quel tipo di perdita di memoria, e poi avere Java o .Net consumarlo. Java e .Net caricano e scaricano le DLL migliaia di volte durante la vita di un programma. Ogni volta che la DLL viene ricaricata, perderà un po 'più di memoria. I programmi a esecuzione prolungata finiranno per esaurire la memoria. Fa impazzire il manutentore di Debian OpenJDK. Lo stesso ha detto sulla mailing list di OpenSSL mentre discutevamo delle perdite di memoria "benigne" di OpenSSL.
jww

10

Dal momento che c'è un po 'di routine dalla famiglia pthread in basso (ma non conosco quella particolare), la mia ipotesi sarebbe che tu abbia lanciato un thread come joinable che ha terminato l'esecuzione.

Le informazioni sullo stato di uscita di quel thread sono mantenute disponibili fino alla chiamata pthread_join. Pertanto, la memoria viene mantenuta in un record di perdita al termine del programma, ma è comunque raggiungibile poiché è possibile utilizzarla pthread_joinper accedervi.

Se questa analisi è corretta, avvia questi thread distaccati o unisciti a loro prima di terminare il programma.

Modifica : ho eseguito il tuo programma di esempio (dopo alcune ovvie correzioni) e non ho errori, ma i seguenti

==18933== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
--18933-- 
--18933-- used_suppression:      2 dl-hack3-cond-1
--18933-- used_suppression:      2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a

Dal momento che la dl-cosa ricorda molto di ciò che vedi, immagino che tu veda un problema noto che ha una soluzione in termini di un file di soppressione per valgrind. Forse il tuo sistema non è aggiornato o la tua distribuzione non mantiene queste cose. (Il mio è Ubuntu 10.4, 64 bit)


Ricevo 0 errori proprio come te. Consultare il riepilogo delle perdite per informazioni sulle "perdite".

@crypto: non capisco. Vuoi dire che hai le stesse soppressioni di me?
Jens Gustedt,

used_suppression: 14 dl-hack3-cond-1 <- questo è quello che ottengo

6

Non sembra capire cosa still reachablesignifichi.

Qualcosa nonstill reachable è una perdita. Non devi fare nulla al riguardo.


24
Ciò è in conflitto con l'altro verbale fornito da Valgrind e tecnicamente errato. La memoria era "ancora raggiungibile" all'uscita dal programma e quindi potenzialmente una perdita. Cosa succede se si esegue il debug del codice per l'esecuzione su un RTOS che non pulisce bene la memoria dopo l'uscita dal programma?
Toymakerii,

4
Sfortunatamente, non è sempre vero. I descrittori di file persi, ad esempio, possono essere considerati perdita di memoria, ma valgrind li classifica come "ancora raggiungibili", presumibilmente perché i puntatori che li conducono sono ancora accessibili all'interno di una tabella di sistema. Ma ai fini del debug, la vera diagnosi è una "perdita di memoria".
Ciano,

I descrittori di file persi non sono perdite di memoria per definizione. Forse stai parlando di FILEpuntatori persi ?
Impiegato russo il

6

Ecco una spiegazione corretta di "ancora raggiungibile":

"Ancora raggiungibili" sono le perdite assegnate alle variabili globali e statiche locali. Poiché valgrind tiene traccia delle variabili globali e statiche, può escludere allocazioni di memoria assegnate "una volta dimentica". Una variabile globale ha assegnato un'allocazione una volta e non ha mai riassegnato che l'allocazione non è in genere una "perdita", nel senso che non cresce indefinitamente. È ancora una perdita in senso stretto, ma di solito può essere ignorato se non si è pedanti.

Le variabili locali a cui vengono assegnate allocazioni e non libere sono quasi sempre delle perdite.

Ecco un esempio

int foo(void)
{
    static char *working_buf = NULL;
    char *temp_buf;
    if (!working_buf) {
         working_buf = (char *) malloc(16 * 1024);
    }
    temp_buf = (char *) malloc(5 * 1024);

    ....
    ....
    ....

}

Valgrind segnalerà working_buf come "ancora raggiungibile - 16k" e temp_buf come "definitivamente perso - 5k".


-1

Per i lettori futuri, "Ancora raggiungibile" potrebbe significare che hai dimenticato di chiudere qualcosa come un file. Anche se non sembra così nella domanda originale, dovresti sempre assicurarti di averlo fatto.

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.