Jak znaleźć wyciek pamięci w kodzie / projekcie C ++?

180

Jestem programistą C ++ na platformie Windows. Używam Visual Studio 2008.

Zazwyczaj kończę w kodzie z przeciekami pamięci.

Zwykle stwierdzam wyciek pamięci poprzez sprawdzenie kodu, ale jest on uciążliwy i nie zawsze jest dobrym podejściem.

Ponieważ nie stać mnie na płatne narzędzie do wykrywania wycieków pamięci, chciałem, abyście zaproponowali najlepsze możliwe sposoby uniknięcia wycieków pamięci.

  1. Chcę wiedzieć, jak programista może wykryć wycieki pamięci.
  2. Czy istnieje jakiś standard lub procedura, którą należy zastosować, aby upewnić się, że nie ma wycieku pamięci w programie?
Chris_vr
źródło
29
„Zazwyczaj kończę w kodzie z wyciekiem pamięci”. Jeśli używasz automatycznych zmiennych, kontenerów i inteligentnych wskaźników (i postępujesz zgodnie z najlepszymi praktykami dotyczącymi używania inteligentnych wskaźników), wycieki pamięci powinny być niezwykle rzadkie. Pamiętaj, że w prawie wszystkich przypadkach powinieneś używać automatycznego zarządzania zasobami .
James McNellis,
Duplikuje problemy objęte kilkoma pytaniami, takimi jak stackoverflow.com/questions/1502799/... i stackoverflow.com/questions/2820223/…
HostileFork mówi, że nie ufaj
1
@Hostile Fork: „jak można uniknąć sytuacji, w której zwykle kończy się wycieki pamięci”, odpowiedzi te nie są objęte.
Dok. Brown
2
@Doc Brown: Nie chciałem też tego szukać, ale wszystko to jest omówione gdzie indziej, takie jak stackoverflow.com/questions/45627/…
HostileFork mówi, że nie ufaj
1
DIY Wykrywacz nieszczelności: możesz umieścić podejrzany kod w nieskończonej pętli, a następnie otworzyć menedżera zadań, zwykle nawet niewielki wyciek zapełni pamięć w ciągu kilku sekund lub minut (zależy to od złożoności kodu i procesora). Jeśli tak się nie stanie, ten fragment kodu prawdopodobnie nie wycieka.
Hello World,

Odpowiedzi:

270

Instrukcje

Rzeczy, których będziesz potrzebować

  • Biegłość w języku C ++
  • Kompilator C ++
  • Debuger i inne narzędzia oprogramowania śledczego

1

Zrozumienie podstaw operatora. Operator C ++ newprzydziela pamięć sterty. Przez deletepamięć Operatora uwalnia sterty. Dla każdego newnależy użyć deletetak, aby zwolnić tę samą pamięć, którą przydzielono:

char* str = new char [30]; // Allocate 30 bytes to house a string.

delete [] str; // Clear those 30 bytes and make str point nowhere.

2)

Przydziel ponownie pamięć tylko po usunięciu. W poniższym kodzie struzyskuje nowy adres z drugim przydziałem. Pierwszy adres zostaje bezpowrotnie utracony, podobnie jak 30 bajtów, które wskazał. Teraz nie można ich uwolnić, a masz przeciek pamięci:

char* str = new char [30]; // Give str a memory address.

// delete [] str; // Remove the first comment marking in this line to correct.

str = new char [60]; /* Give str another memory address with
                                                    the first one gone forever.*/

delete [] str; // This deletes the 60 bytes, not just the first 30.

3)

Obejrzyj te przypisania wskaźnika. Każda zmienna dynamiczna (przydzielona pamięć na stercie) musi być powiązana ze wskaźnikiem. Kiedy zmienna dynamiczna zostaje odłączona od swojego wskaźnika (wskaźników), niemożliwe jest usunięcie. Ponownie powoduje to wyciek pamięci:

char* str1 = new char [30];

char* str2 = new char [40];

strcpy(str1, "Memory leak");

str2 = str1; // Bad! Now the 40 bytes are impossible to free.

delete [] str2; // This deletes the 30 bytes.

delete [] str1; // Possible access violation. What a disaster!

4

Uważaj na lokalne wskaźniki. Wskaźnik zadeklarowany w funkcji jest alokowany na stosie, ale zmienna dynamiczna, na którą wskazuje, jest alokowana na stercie. Jeśli go nie usuniesz, będzie się utrzymywać po wyjściu programu z funkcji:

void Leak(int x){

char* p = new char [x];

// delete [] p; // Remove the first comment marking to correct.

}

5

Zwróć uwagę na kwadratowe nawiasy klamrowe po „usuń”. Skorzystaj deletesam z siebie, aby uwolnić pojedynczy obiekt. Użyj delete []z nawiasami kwadratowymi, aby zwolnić tablicę sterty. Nie rób czegoś takiego:

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!

6

Jeśli przeciek jest jeszcze dozwolony - zwykle szukam go w deleaker (sprawdź tutaj: http://deleaker.com ).

John Smith
źródło
3
przepraszam za pytanie-komentarz, ale co z parametrami funkcji bez wskaźników? someFunction("some parameter")czy muszę usunąć "some parameter"w someFunction, po wywołaniu funkcji, czy też są one automatycznie usuwane?
19greg96
1
dziękuję za link do Deleakera, jest to naprawdę przydatne narzędzie ze zgrabną integracją ze studiem wizualnym. Korzystając z niego, mogłem zaoszczędzić dużo czasu. wskazał mi linie, w których przydzieliłem pamięć i jej nie zwolniłem. Wspaniały. I jest tani, w porównaniu z innymi wykrywaczami nieszczelności pamięci, które znalazłem.
this.myself
@ John Smith plz wyjaśnij, jaki jest właściwy sposób obsługi przypadków podobnych do przypadku 3; str2 = str1; // Źle! Teraz 40 bajtów jest niemożliwych do uwolnienia. jak usunąć następnie str 1?
Nihar
1
Co się stanie, jeśli użyjemy typu wartości, takiego jak char *, int, float, ... i struct jak Vector, CString i nie użyjemy żadnego „nowego” operatora, to nie spowoduje przecieku pamięci, prawda?
123iamking
Jestem tutaj, aby powiedzieć, że nie dotknąłem c ++ od prawie 14 lat ... ale z dumą mogę powiedzieć, że zrozumiałem i pamiętam, jak to wszystko zrobić dzięki książce c ++, którą wciąż posiadam i czytam, kiedy „ nudzi mi się c #. Ta książka to Effective C ++ Scotta Mitchella. Boże, kochałem tę książkę. Dzięki Scott!
JonH
33

Możesz użyć niektórych technik w kodzie, aby wykryć wyciek pamięci. Najczęstszym i najłatwiejszym sposobem na wykrycie jest zdefiniowanie makra powiedz DEBUG_NEW i użycie go wraz ze wstępnie zdefiniowanymi makrami, takimi jak __FILE__i __LINE__zlokalizowanie wycieku pamięci w kodzie. Te predefiniowane makra informują o wycieku pliku i linii.

DEBUG_NEW to po prostu MAKRO, które zwykle definiuje się jako:

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

Dzięki temu, gdziekolwiek użyjesz new, może również śledzić numer pliku i linii, które mogą być użyte do zlokalizowania wycieku pamięci w twoim programie.

I __FILE__, __LINE__predefiniowanymi makrami, które oceniają odpowiednio nazwę pliku i numer wiersza, gdzie ich używasz!

Przeczytaj następujący artykuł, który wyjaśnia technikę używania DEBUG_NEW z innymi interesującymi makrami, bardzo pięknie:

Wieloplatformowy wykrywacz wycieków pamięci


Z Wikpedii ,

Debug_new odnosi się do techniki w C ++ polegającej na przeciążeniu i / lub przedefiniowaniu operatora new i operator delete w celu przechwycenia wywołań alokacji pamięci i dezalokacji, a tym samym debugowania programu do wykorzystania pamięci. Często wymaga zdefiniowania makra o nazwie DEBUG_NEW i sprawia, że ​​nowe staje się czymś nowym (_ PLIK _, _ LINIA _) do zapisywania informacji o pliku / linii przy alokacji.Microsoft Visual C ++ używa tej techniki w swoich klasach Microsoft Foundation. Istnieje kilka sposobów rozszerzenia tej metody, aby uniknąć korzystania z redefinicji makr, a jednocześnie móc wyświetlać informacje o pliku / linii na niektórych platformach. Istnieje wiele nieodłącznych ograniczeń tej metody. Ma zastosowanie tylko do C ++ i nie może wykryć wycieków pamięci przez funkcje C, takie jak malloc. Jednak może być bardzo prosty w użyciu, a także bardzo szybki, w porównaniu z bardziej kompletnymi rozwiązaniami do debugowania pamięci.

Nawaz
źródło
4
#definespowoduje to bałagan z przeciążeniem operator newi wygeneruje błędy kompilatora. Nawet jeśli uda ci się to pokonać, nadal przeciążone funkcje nie zostaną rozwiązane. Chociaż technika jest dobra, czasem wymaga wielu zmian w kodzie.
iammilind
1
@iammilind: Oczywiście ta technika nie rozwiązuje wszystkich problemów i na pewno nie ma zastosowania we wszystkich sytuacjach.
Nawaz
@Chris_vr: auto_ptrnie będzie działać z standardowych pojemnikach, takich jak std::vector, std::listitd. Zobacz to: stackoverflow.com/questions/111478/...
Nawaz
Ok spoko. PLIK i wiersz są opisane. Co to jest operator newi jakich wersji używasz?
14

Istnieje kilka dobrze znanych technik programowania, które pomogą zminimalizować ryzyko wycieków pamięci z pierwszej ręki:

  • jeśli musisz wykonać własną dynamiczną alokację pamięci, napisz newi deletezawsze paruj, i upewnij się, że kod alokacji / dezalokacji nazywa się parami
  • jeśli to możliwe, unikaj dynamicznego przydzielania pamięci. Na przykład użyj vector<T> tzamiast tego gdziekolwiek to możliweT* t = new T[size]
  • używaj „inteligentnych wskaźników”, takich jak inteligentne wskaźniki zwiększające ( http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm )
  • mój osobisty faworyt: upewnij się, że rozumiesz pojęcie własności wskaźnika i upewnij się, że wszędzie tam, gdzie używasz wskaźników, wiesz, która jednostka kodu jest właścicielem
  • dowiedz się, które konstruktory / operatory przypisania są automatycznie tworzone przez kompilator C ++ i co to oznacza, jeśli masz klasę, która jest właścicielem wskaźnika (lub co to znaczy, jeśli masz klasę, która zawiera wskaźnik do obiektu, którego nie ma ).
Doktor Brown
źródło
Używam auto_pointer obiektu, co oznacza, że ​​usunie on wszystkie inne wskaźniki obiektów klasy wewnątrz niego.
Chris_vr
@Chris_vr: jeśli masz konkretne pytanie dotyczące auto_pointer, sugeruję, abyś zadał nowe pytanie, w tym przykład.
Doc Brown
Wiele postów mówi mi, że wektor <> nie gwarantuje zwolnienia pamięci po wyczyszczeniu. Osobiście przetestowałem swap itp. I doszedłem do wniosku, że wektor <> przecieka, szczególnie gdy jest używany dynamicznie. Nie rozumiem, w jaki sposób radzić się wektorowi <> zamiast dynamicznego przydzielania zrób to sam, używając poprawnie 'nowego' i czyszczenia. W moich programach wbudowanych unikam używania wektora <> do dynamicznych rzeczy z powodu wszystkich przecieków. Tam używam new lub std :: list
bart,
Wpisuję drugie polecenie ze względu na liczbę znaków. Niestety w moim wbudowanym c ++ mam starą wersję c ++ (98?), Która nie ma elementu shrink_to_fit na wektorze ... Jednak wbudowany program jest w 100% pewny, że całkowicie się zawiesi, gdy zabraknie pamięci za pomocą wektora <> dynamicznie
bart s
8
  1. Pobierz Narzędzia do debugowania dla systemu Windows .
  2. Użyj gflagsnarzędzia, aby włączyć ślady stosu w trybie użytkownika.
  3. Służy UMDHdo robienia wielu migawek pamięci programu. Zrób migawkę, zanim pamięć zostanie przydzielona, ​​i zrób drugą migawkę po punkcie, w którym uważasz, że program wyciekł z pamięci. Możesz dodać przerwy lub monity w swoim programie, aby dać Ci szansę na uruchomienie UMDHi zrobienie migawek.
  4. Uruchom UMDHponownie, tym razem w trybie, który różni się między dwoma migawkami. Następnie wygeneruje raport zawierający stosy wywołań podejrzanych wycieków pamięci.
  5. Po zakończeniu przywróć poprzednie gflagsustawienia.

UMDHdostarczy ci więcej informacji niż sterty debugowania CRT, ponieważ obserwuje alokacje pamięci w całym procesie; może nawet powiedzieć, czy przeciekają komponenty innych firm.

Aaron Klotz
źródło
1
Wolę Deleaker i Valgrind zamiast standardowego profilera
z0r1fan
8

Uruchomienie „Valgrind” może:

1) Pomóż zidentyfikować wycieki pamięci - pokaż, ile masz wycieków pamięci, i wskaż wiersze w kodzie, w którym przydzielono wyciek pamięci.

2) Wskaż niewłaściwe próby zwolnienia pamięci (np. Niewłaściwe wywołanie delete)

Instrukcje korzystania z „Valgrind”

1) Zdobądź valgrind tutaj .

2) Skompiluj swój kod z -gflagą

3) W biegu powłoki:

valgrind --leak-check=yes myprog arg1 arg2

Gdzie „myprog” to skompilowany program i arg1, arg2argumenty za swój program.

4) Wynikiem jest lista połączeń do malloc/, newktóre nie miały kolejnych połączeń do darmowego usunięcia.

Na przykład:

==4230==    at 0x1B977DD0: malloc (vg_replace_malloc.c:136)

==4230==    by 0x804990F: main (example.c:6)

Mówi, w której linii malloczostała wywołana (która nie została uwolniona).

Jak wskazali inni, upewnij się, że dla każdego new/ mallocpołączenia masz kolejne delete/ freepołączenie.

Gal Nachmana
źródło
6

Jeśli używasz gcc, dostępny jest gprof.

Chciałem wiedzieć, jak programista znajduje wyciek pamięci

Niektóre używają narzędzi, inne robią to, co robisz, mogą również przeglądać kod równorzędny

Czy istnieje jakakolwiek norma lub procedura, którą należy zastosować, aby upewnić się, że nie ma wycieku pamięci w programie

Dla mnie: za każdym razem, gdy tworzę dynamicznie przydzielane obiekty, zawsze umieszczam kod zwalniający po, a następnie wypełniam kod pomiędzy. Byłoby to OK, jeśli jesteś pewien, że między kodem nie będzie wyjątków. W przeciwnym razie korzystam z try-wreszcie (nie używam często C ++).

LeleDumbo
źródło
jakiś czas nie możemy usunąć przydzielonego w konstruktorze. co zrobić przy tej okazji.
Chris_vr
5
  1. W studiu wizualnym jest wbudowany detektor wycieku pamięci o nazwie C Runtime Library. Kiedy program zakończy działanie po powrocie funkcji głównej, CRT sprawdzi stertę debugowania aplikacji. jeśli nadal masz przydzielone bloki na stercie debugowania, oznacza to przeciek pamięci.

  2. To forum omawia kilka sposobów uniknięcia wycieku pamięci w C / C ++ ..

Benny Tjia
źródło
5

Wyszukaj w kodzie wystąpienia newi upewnij się, że wszystkie występują w konstruktorze z pasującym usunięciem w destruktorze. Upewnij się, że jest to jedyna możliwa operacja wyrzucania w tym konstruktorze. Prostym sposobem na to jest zawinięcie wszystkich wskaźników std::auto_ptrlub boost::scoped_ptr(w zależności od tego, czy potrzebujesz przesunąć semantykę). Dla całego przyszłego kodu po prostu upewnij się, że każdy zasób jest własnością obiektu, który czyści zasób w swoim destruktorze. Jeśli potrzebujesz semantyki przenoszenia, możesz uaktualnić do kompilatora, który obsługuje odwołania r-wartość (wierzę VS2010) i tworzyć konstruktory przenoszenia. Jeśli nie chcesz tego robić, możesz użyć różnych podchwytliwych technik polegających na sumiennym użyciu wymiany lub wypróbować bibliotekę Boost.Move.

Mankarse
źródło
nie zawsze jest możliwe usunięcie przydzielonej pamięci w konstruktorze.Jak poradzić sobie z tą sytuacją
Chris_vr
@Chris_vr Co masz na myśli? Jeśli wszyscy członkowie wskaźnika są scope_ptrs i każdy jest inicjowany indywidualnie, wszystkie te, które zostały pomyślnie skonstruowane, usuną swoje wskaźniki, a pozostałe i tak nie będą jeszcze utrzymywać wskaźników w przydzielonej pamięci. Podam przykład za kilka godzin, kiedy wrócę z pracy do domu.
Mankarse
@Chris_vr: jeśli masz konkretny przykład, opublikuj go jako nowe pytanie, abyśmy mogli go tam przedyskutować.
Dok. Brown
5

Możesz użyć narzędzia Valgrind do wykrywania wycieków pamięci.

Ponadto, aby znaleźć wyciek w określonej funkcji, użyj wyjścia (0) na końcu funkcji, a następnie uruchom ją za pomocą Valgrind

`$` valgrind ./your_CPP_program 
Divyanshu
źródło
5

Przegląd automatycznych kontrolerów wycieków pamięci

W tej odpowiedzi porównuję kilka różnych kontrolerów wycieków pamięci w prostym, łatwym do zrozumienia przykładzie wycieku pamięci.

Przede wszystkim zobacz tę ogromną tabelę na wiki ASan, która porównuje wszystkie narzędzia znane człowiekowi: https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924

Analizowany przykład to:

main.c

#include <stdlib.h>

void * my_malloc(size_t n) {
    return malloc(n);
}

void leaky(size_t n, int do_leak) {
    void *p = my_malloc(n);
    if (!do_leak) {
        free(p);
    }
}

int main(void) {
    leaky(0x10, 0);
    leaky(0x10, 1);
    leaky(0x100, 0);
    leaky(0x100, 1);
    leaky(0x1000, 0);
    leaky(0x1000, 1);
}

GitHub w górę .

Postaramy się zobaczyć, jak wyraźnie różne narzędzia wskazują nam na nieszczelne połączenia.

tcmalloc z gperftools od Google

https://github.com/gperftools/gperftools

Zastosowanie na Ubuntu 19.04:

sudo apt-get install google-perftools
gcc -ggdb3 -o main.out main.c -ltcmalloc
PPROF_PATH=/usr/bin/google-pprof \
  HEAPCHECK=normal \
  HEAPPROFILE=ble \
  ./main.out \
;
google-pprof main.out ble.0001.heap --text

Dane wyjściowe uruchomienia programu zawierają analizę wycieków pamięci:

WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to ble.0001.heap (Exiting, 4 kB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 272 bytes in 2 objects
The 2 largest leaks:
Using local file ./main.out.
Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./main.out "/tmp/main.out.24744._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more re
Exiting with error code (instead of crashing) because of whole-program memory leaks

a wynik google-pprofzawiera analizę użycia sterty:

Using local file main.out.
Using local file ble.0001.heap.
Total: 0.0 MB
     0.0 100.0% 100.0%      0.0 100.0% my_malloc
     0.0   0.0% 100.0%      0.0 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.0 100.0% _start
     0.0   0.0% 100.0%      0.0 100.0% leaky
     0.0   0.0% 100.0%      0.0 100.0% main

Dane wyjściowe wskazują nam dwa z trzech wycieków:

Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start

Nie jestem pewien, dlaczego trzeci się nie pojawił

W każdym razie, gdy zwykle, gdy coś wycieka, zdarza się to wiele razy, a kiedy użyłem go w prawdziwym projekcie, po prostu bardzo łatwo zauważyłem funkcję przecieku.

Jak wspomniano na samym wyjściu, powoduje to znaczne spowolnienie wykonania.

Dalsza dokumentacja na:

Zobacz także: Jak korzystać z TCMalloc?

Testowany w Ubuntu 19.04, google-perftools 2.5-2.

Adres Sanitizer (ASan) również przez Google

https://github.com/google/sanitizers

Wcześniej wspomniano w: Jak znaleźć wyciek pamięci w kodzie / projekcie C ++? TODO vs tcmalloc.

Jest to już zintegrowane z GCC, więc możesz po prostu:

gcc -fsanitize=address -ggdb3 -o main.out main.c
./main.out 

i wyniki wykonania:

=================================================================
==27223==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4096 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f210 in main /home/ciro/test/main.c:20
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 256 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1f2 in main /home/ciro/test/main.c:18
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1d4 in main /home/ciro/test/main.c:16
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

SUMMARY: AddressSanitizer: 4368 byte(s) leaked in 3 allocation(s).

który wyraźnie identyfikuje wszystkie wycieki. Miły!

ASan może również wykonywać inne fajne kontrole, takie jak zapisywanie poza granicami: Wykryto rozbicie stosu

Testowane w Ubuntu 19.04, GCC 8.3.0.

Valgrind

http://www.valgrind.org/

Wcześniej wspomniano na stronie : https://stackoverflow.com/a/37661630/895245

Stosowanie:

sudo apt-get install valgrind
gcc -ggdb3 -o main.out main.c
valgrind --leak-check=yes ./main.out

Wynik:

==32178== Memcheck, a memory error detector
==32178== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==32178== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==32178== Command: ./main.out
==32178== 
==32178== 
==32178== HEAP SUMMARY:
==32178==     in use at exit: 4,368 bytes in 3 blocks
==32178==   total heap usage: 6 allocs, 3 frees, 8,736 bytes allocated
==32178== 
==32178== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091B4: main (main.c:16)
==32178== 
==32178== 256 bytes in 1 blocks are definitely lost in loss record 2 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091D2: main (main.c:18)
==32178== 
==32178== 4,096 bytes in 1 blocks are definitely lost in loss record 3 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091F0: main (main.c:20)
==32178== 
==32178== LEAK SUMMARY:
==32178==    definitely lost: 4,368 bytes in 3 blocks
==32178==    indirectly lost: 0 bytes in 0 blocks
==32178==      possibly lost: 0 bytes in 0 blocks
==32178==    still reachable: 0 bytes in 0 blocks
==32178==         suppressed: 0 bytes in 0 blocks
==32178== 
==32178== For counts of detected and suppressed errors, rerun with: -v
==32178== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

Po raz kolejny wszystkie wycieki zostały wykryte.

Zobacz także: Jak używać valgrind do wykrywania wycieków pamięci?

Testowane w Ubuntu 19.04, valgrind 3.14.0.

Ciro Santilli
źródło
4

Visual Leak Detector (VLD) to darmowy, solidny system wykrywania wycieków pamięci typu open source dla Visual C ++.

Po uruchomieniu programu za pomocą debugera Visual Studio, Visual Leak Detector wyświetli raport przecieku pamięci na koniec sesji debugowania. Raport wycieku zawiera pełny stos wywołań pokazujący, w jaki sposób zostały przydzielone wyciekające bloki pamięci. Kliknij dwukrotnie linię na stosie wywołań, aby przejść do tego pliku i linii w oknie edytora.

Jeśli masz tylko zrzuty awaryjne, możesz użyć !heap -lpolecenia Windbg , wykryje ono wycieki bloków sterty. Lepiej otwórz opcję gflags: „Utwórz bazę danych śledzenia stosu trybu użytkownika”, wtedy zobaczysz stos wywołań alokacji pamięci.

świeży
źródło
4

MTuner to bezpłatne narzędzie do profilowania pamięci, wykrywania i analizy wycieków z wielu platform, obsługujące kompilatory MSVC, GCC i Clang. Dodatki zawarte:

  • oparta na osi czasu historia użycia pamięci i bloki pamięci na żywo
  • potężne filtrowanie operacji pamięci na podstawie sterty, znacznika pamięci, zakresu czasu itp.
  • SDK do ręcznego oprzyrządowania z pełnym kodem źródłowym
  • ciągła obsługa integracji poprzez użycie wiersza poleceń
  • drzewo stosu połączeń i nawigacja po mapie drzewa
  • wiele więcej.

Użytkownicy mogą profilować dowolne oprogramowanie atakujące platformy za pomocą kompilatorów krzyżowych GCC lub Clang. MTuner ma wbudowaną obsługę platform Windows, PlayStation 4 i PlayStation 3.

mtosic
źródło
To powinna być zaakceptowana odpowiedź. Jest to świetne narzędzie i może obsłużyć liczbę przydziałów / zwolnień, których inni nie mogą.
Serge Rogatch
3

W systemie Windows można użyć sterty debugowania CRT .

Czy istnieje jakakolwiek norma lub procedura, którą należy zastosować, aby upewnić się, że nie ma wycieku pamięci w programie.

Tak, nie używaj ręcznego zarządzania pamięcią (jeśli kiedykolwiek zadzwonisz deletelub delete[]ręcznie, to robisz to źle). Używaj wskaźników RAII i inteligentnych, ogranicz alokacje sterty do absolutnego minimum (przez większość czasu wystarczą zmienne automatyczne).

Cat Plus Plus
źródło
3

Odpowiadając na drugą część pytania,

Czy istnieje jakakolwiek norma lub procedura, którą należy zastosować, aby upewnić się, że nie ma wycieku pamięci w programie.

Tak jest. Jest to jedna z kluczowych różnic między C i C ++.

W języku C ++ nigdy nie należy wywoływać newani deletewprowadzać kodu użytkownika. RAII jest bardzo często stosowaną techniką, która właściwie rozwiązuje problem zarządzania zasobami. Każdy zasób w twoim programie (zasób to wszystko, co należy nabyć, a następnie uwolnić: uchwyty plików, gniazda sieciowe, połączenia z bazą danych, ale także zwykłe przydziały pamięci, a w niektórych przypadkach pary wywołań API (BeginX ( ) / EndX (), LockY (), UnlockY ()), powinny być opakowane w klasę, w której:

  • konstruktor nabywa zasób (wywołującnew jeśli zasób jest alokacją memroy)
  • destruktor uwalnia zasób,
  • kopiowanie i przypisywanie jest albo uniemożliwione (przez ustawienie konstruktora kopiowania i operatorów przypisania jako prywatne), albo są zaimplementowane w celu poprawnego działania (na przykład przez klonowanie zasobu podstawowego)

Ta klasa jest następnie tworzona lokalnie, na stosie lub jako członek klasy, a nie przez wywoływanie newi przechowywanie wskaźnika.

Często nie musisz samodzielnie definiować tych klas. Standardowe kontenery biblioteczne zachowują się również w ten sposób, dzięki czemu każdy obiekt przechowywany w plikustd::vector zostanie zwolniony po zniszczeniu wektora. Więc jeszcze raz, nie przechowywać wskaźnik do pojemnika (który musiałby pan zadzwonić newi delete), ale raczej obiekt sam (co daje zarządzanie pamięcią za darmo ). Podobnie, inteligentne klasy wskaźników mogą być używane do łatwego zawijania obiektów, które muszą zostać przydzielone newi kontrolowania ich żywotności.

Oznacza to, że gdy obiekt wykracza poza zakres, zostaje automatycznie zniszczony, a jego zasoby zwolnione i oczyszczone.

Jeśli robisz to konsekwentnie w całym kodzie, po prostu nie będziesz mieć przecieków pamięci. Wszystko co może wyciec, jest powiązane z destruktorem, który z pewnością zostanie wywołany, gdy kontrola opuści zakres, w którym obiekt został zadeklarowany.

jalf
źródło
jeśli inteligentny wskaźnik zawiera klasę, a klasa ta zawiera wskaźnik kilku innych klas. kiedy smart gaśnie, oznacza to, że cały wskaźnik wewnątrz zostanie bezpiecznie usunięty.
Chris_vr
@Chris: Zakładając, że obiekt wskazywany przez inteligentny wskaźnik ma destruktor, który wykonuje niezbędne czyszczenie lub obiekt zawiera elementy, które same mają destruktory, aby wykonać niezbędne czyszczenie. Zasadniczo, o ile każdy obiekt dba o siebie (oczyszcza się po sobie, gdy jest zniszczony) i tak długo, jak każdy obiekt jest przechowywany pod względem wartości, a nie jako wskaźnik, wówczas wszystko, co trzeba uwolnić, zostanie uwolnione.
jalf
3

AddressSanitizer (ASan) to szybki wykrywacz błędów pamięci. Znajduje błędy przepełnienia bufora „use-after-free” i {heap, stack, global} w programach C / C ++. Znajduje:

  • Użyj za darmo (zwisające odniesienie wskaźnika)
  • Przepełnienie bufora sterty
  • Przepełnienie bufora stosu
  • Globalny przepełnienie bufora
  • Użyj po powrocie
  • Błędy kolejności inicjalizacji

To narzędzie jest bardzo szybkie. Średnie spowolnienie instrumentowanego programu wynosi ~ 2x.

Początkujący
źródło
Zobacz zwłaszcza LeakSanitizer
Gabriel Devillers
0

Poza narzędziami i metodami opisanymi w innych odpowiedziach, do wykrywania wycieków pamięci (i innych problemów) można także użyć narzędzi do analizy kodu statycznego. Bezpłatne, niezawodne narzędzie to Cppcheck. Istnieje jednak wiele innych dostępnych narzędzi. Wikipedia ma listę narzędzi do analizy kodu statycznego.

orbitcowboy
źródło
-1

Upewnij się, że cała pamięć sterty została pomyślnie zwolniona. Nie ma potrzeby, jeśli nigdy nie przydzielisz pamięci na stercie. Jeśli to zrobisz, policz, ile razy pamięć malloc została zliczona, i policz ilość wolnego czasu pamięci.


źródło
-3

W kodzie aplikacji nie należy nigdy używać „nowego” ani „skasować”. Zamiast tego utwórz nowy typ korzystający z idiomu menedżer / pracownik, w którym klasa menedżera przydziela i zwalnia pamięć oraz przekazuje wszystkie inne operacje do obiektu roboczego.

Niestety jest to więcej pracy, niż powinno być, ponieważ C ++ nie ma przeciążenia „operatora”. To jeszcze więcej pracy w obecności polimorfizmu.

Ale to jest warte wysiłku, ponieważ nigdy nie musisz się martwić o wycieki pamięci, co oznacza, że ​​nawet nie musisz ich szukać.

s. halerz
źródło