Wyciek wciąż osiągalny wykryty przez Valgrinda

154

Wszystkie funkcje wymienione w tym bloku są funkcjami bibliotecznymi. Jak mogę naprawić ten wyciek pamięci?

Znajduje się w kategorii „ Nadal osiągalne ”. (Są jeszcze 4, które są bardzo podobne, ale o różnych rozmiarach)

 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)

Catch: Kiedy uruchomiłem mój program, nie powodował wycieków pamięci, ale miał jedną dodatkową linię na wyjściu Valgrind, której wcześniej nie było:

Odrzucanie symboli pod adresem 0x5296fa0-0x52af438 w /lib/libgcc_s-4.4.4-20100630.so.1 z powodu munmap ()

Jeśli wycieku nie można naprawić, czy ktoś może przynajmniej wyjaśnić, dlaczego wiersz munmap () powoduje, że Valgrind zgłasza 0 przecieków „nadal osiągalnych”?

Edytować:

Oto minimalna próbka testowa:

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

Biegnij z:

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

źródło
Valgrind FAQ .
jww

Odpowiedzi:

378

Istnieje więcej niż jeden sposób zdefiniowania „wycieku pamięci”. W szczególności istnieją dwie podstawowe definicje „wycieku pamięci”, które są powszechnie używane przez programistów.

Pierwsza powszechnie stosowana definicja „wycieku pamięci” brzmi: „Pamięć została przydzielona i nie została następnie zwolniona przed zakończeniem programu”. Jednak wielu programistów (słusznie) twierdzi, że pewne typy wycieków pamięci, które pasują do tej definicji, w rzeczywistości nie stanowią żadnego problemu i dlatego nie powinny być uważane za prawdziwe „wycieki pamięci”.

Prawdopodobnie bardziej rygorystyczna (i bardziej użyteczna) definicja „wycieku pamięci” brzmi: „Pamięć została przydzielona i nie można jej później zwolnić, ponieważ program nie ma już żadnych wskaźników do przydzielonego bloku pamięci”. Innymi słowy, nie możesz zwolnić pamięci, do której nie masz już żadnych wskazówek. Taka pamięć jest zatem „wyciekiem pamięci”. Valgrind używa ściślejszej definicji terminu „wyciek pamięci”. Jest to rodzaj wycieku, który może potencjalnie spowodować znaczne zubożenie hałdy, szczególnie w przypadku długotrwałych procesów.

Kategoria „nadal osiągalne” w raporcie przecieków Valgrind odnosi się do przydziałów, które pasują tylko do pierwszej definicji „wycieku pamięci”. Bloki te nie zostały zwolnione, ale mogły zostać zwolnione (jeśli programista chciał), ponieważ program nadal śledził wskaźniki do tych bloków pamięci.

Ogólnie rzecz biorąc, nie ma potrzeby martwić się o „wciąż dostępne” bloki. Nie stanowią problemu, który mogą powodować prawdziwe wycieki pamięci. Na przykład normalnie nie ma możliwości wyczerpania sterty przez „wciąż osiągalne” bloki. Dzieje się tak, ponieważ bloki te są zwykle jednorazowymi alokacjami, do których odniesienia są przechowywane przez cały czas trwania procesu. Chociaż możesz przejść przez to i upewnić się, że program zwolni całą przydzieloną pamięć, zwykle nie ma to praktycznej korzyści, ponieważ system operacyjny i tak odzyska całą pamięć procesu po zakończeniu procesu. Porównaj to z prawdą przecieki pamięci, które pozostawione nienaprawione mogą spowodować, że procesowi zabraknie pamięci, jeśli będzie działał wystarczająco długo, lub po prostu spowodują, że proces zużyje znacznie więcej pamięci niż jest to konieczne.

Prawdopodobnie jedyny moment, w którym warto upewnić się, że wszystkie alokacje mają pasujące „zwolnienia”, to sytuacja, gdy narzędzia do wykrywania wycieków nie mogą określić, które bloki są „nadal osiągalne” (ale Valgrind może to zrobić) lub jeśli system operacyjny nie odzyskuje wszystkich pamięć procesu kończącego (wszystkie platformy, na które przeportował Valgrind).

Dan Moulding
źródło
Czy możesz przypuszczać, co robi munmap (), co powoduje, że „wciąż osiągalne” bloki znikają?
3
@crypto: Możliwe, że munmapjest wywoływany w wyniku wyładowania udostępnionego obiektu. Wszystkie zasoby używane przez obiekt współdzielony mogą zostać zwolnione, zanim zostanie wyładowany. To może wyjaśniać, dlaczego „nadal osiągalne” są zwalniane w tej munmapsprawie. Jednak tylko spekuluję. Nie ma tu wystarczających informacji, aby z pewnością powiedzieć.
Dan Molding
3
Jeden przypadek, w którym „wciąż osiągalna” pamięć może być uznana za przeciek pamięci: załóżmy, że masz tablicę mieszającą, w której dodajesz wskaźniki do pamięci przydzielonej na stos jako wartość. Jeśli będziesz nadal wstawiać nowe wpisy do tabeli, ale nie usuniesz i nie zwolnisz tych, których już nie potrzebujesz, może to rosnąć w nieskończoność, przeciekając zdarzenie pamięci sterty, jeśli ta pamięć jest „nadal dostępna”. Jest to przypadek wycieku pamięci, który może wystąpić w Javie lub innych językach ze śmieciami.
lvella,
Zobacz także tę odpowiedź w FAQ Valgrind na temat bloków „nadal osiągalnych” tworzonych przez STL. valgrind.org/docs/manual/faq.html#faq.reports
John Perry
5
„Wielu programistów (słusznie) twierdzi, że [wyciek pamięci] w rzeczywistości nie stanowi [problemu] i dlatego nie powinien być traktowany jako prawdziwy wyciek pamięci” - Lol ... Zbuduj natywną bibliotekę DLL z tego rodzaju wyciekiem pamięci, a następnie niech Java lub .Net to wykorzystają. Java i .Net ładują i zwalniają biblioteki DLL tysiące razy w trakcie życia programu. Za każdym razem, gdy biblioteka DLL zostanie ponownie załadowana, wycieknie trochę więcej pamięci. Długo działające programy w końcu zabraknie pamięci. To doprowadza do szału opiekuna Debiana OpenJDK. Powiedział to samo na liście mailingowej OpenSSL, kiedy omawialiśmy „łagodne” wycieki pamięci w OpenSSL.
jww
10

Ponieważ na dole znajduje się procedura z rodziny pthread (ale nie znam tej konkretnej), przypuszczam, że uruchomiłeś wątek jako łączony, który zakończył wykonywanie.

Informacje o stanie wyjścia tego wątku są dostępne do momentu wywołania pthread_join. W ten sposób pamięć jest przechowywana w rekordzie utraty po zakończeniu programu, ale nadal jest dostępna, ponieważ można ją wykorzystać, pthread_joinaby uzyskać do niej dostęp.

Jeśli ta analiza jest prawidłowa, uruchom te wątki odłączone lub dołącz do nich przed zakończeniem programu.

Edycja : uruchomiłem twój przykładowy program (po kilku oczywistych poprawkach) i nie mam błędów, ale następujące

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

Ponieważ dl-rzecz przypomina wiele z tego, co widzisz, myślę, że widzisz znany problem, który ma rozwiązanie w postaci pliku tłumienia valgrind. Być może Twój system nie jest aktualny lub Twoja dystrybucja nie obsługuje tych rzeczy. (Mój to ubuntu 10.4, 64-bitowy)

Jens Gustedt
źródło
Otrzymuję 0 błędów, tak jak Ty. Sprawdź podsumowanie wycieków, aby uzyskać informacje na temat „wycieków”.
@crypto: Nie rozumiem. Masz na myśli te same ograniczenia co ja?
Jens Gustedt
used_suppression: 14 dl-hack3-cond-1 <- to właśnie dostaję
6

Wydaje się, że nie rozumiesz, co still reachableto znaczy.

Wszystko still reachableto nie wyciek. Nie musisz nic z tym robić.

Zatrudniony Rosjanin
źródło
24
Jest to sprzeczne z inną wersją dostarczoną przez Valgrind, a także technicznie niepoprawne. Pamięć była „nadal osiągalna” przy wyjściu z programu, a zatem potencjalnie przecieka. Co by było, gdybyś debugował kod do uruchomienia w systemie RTOS, który nie czyści dobrze pamięci po zakończeniu programu?
Toymakerii
4
Niestety nie zawsze jest to prawdą. Na przykład deskryptory utraconych plików mogą być liczone jako przecieki pamięci, ale Valgrind klasyfikuje je jako „nadal osiągalne”, prawdopodobnie dlatego, że prowadzące do nich wskaźniki są nadal dostępne w tabeli systemowej. Jednak dla celów debugowania, prawdziwą diagnozą jest „wyciek pamięci”.
Cyjan
Deskryptory utraconych plików nie są z definicji wyciekami pamięci. Może mówisz o zagubionych FILEwskazówkach?
Zatrudniony w Rosji
6

Oto prawidłowe wyjaśnienie terminu „nadal osiągalny”:

„Wciąż osiągalne” to przecieki przypisane do zmiennych globalnych i statyczno-lokalnych. Ponieważ valgrind śledzi zmienne globalne i statyczne, może wykluczyć alokacje pamięci, które są przypisane „raz i zapomnij”. Zmienna globalna, która raz przypisała alokację i nigdy jej nie zmieniała, zazwyczaj nie jest „wyciekiem” w tym sensie, że nie rośnie w nieskończoność. Nadal jest to przeciek w ścisłym tego słowa znaczeniu, ale zwykle można go zignorować, chyba że jesteś pedantyczny.

Zmienne lokalne, którym przypisano alokacje i które nie są wolne, są prawie zawsze przeciekami.

Oto przykład

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 zgłosi working_buf jako „nadal osiągalny - 16k”, a temp_buf jako „zdecydowanie utracony - 5k”.

Abbey Road
źródło
-1

Dla przyszłych czytelników „Ciągle osiągalny” może oznaczać, że zapomniałeś zamknąć coś takiego jak plik. Chociaż nie wydaje się to w pierwotnym pytaniu, zawsze powinieneś się upewnić, że to zrobiłeś.

MonerosKin
źródło