Dziś o narzędziu, które pozwoli Ci poradzić sobie z błędami związanymi z pamięcią, np. zlokalizować wycieki. Czyli zbawienie dla Programisty C++ ;)
Szybki przykład z wyciekiem pamięci
Mamy programik z wyciekiem pamięci:
// program.cpp void func() { char *str = new char[10]; } int main() { func(); return 0; }
1. Najpierw kompilujemy go w trybie debugowania z wyłączoną optymalizacją:
> g++ -g -O0 -o program program.cpp
(Jeśli nie znasz kompilatora g++, zobacz Kompilator g++)
2. Następnie odpalamy go tak jak zwykle, tylko że pod kontrolą Valgrinda:
> valgrind --tool=memcheck --leak-check=full ./program
Flaga –-tool=memcheck oznacza, że chcemy użyć narzędzia do debugowania pamięci, a flaga –leak-check=full oznacza, że szczególnie interesują nas informacje na temat wycieków pamięci.
3. W rezultacie dostajemy taki oto komunikat:
==31221== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==31221== at 0x40057F5: operator new[](unsigned) (vg_replace_malloc.c:195)
==31221== by 0x8048485: func() (program.cpp:4)
==31221== by 0x80484A1: main (program.cpp:9)
==31221==
==31221== LEAK SUMMARY:
==31221== definitely lost: 10 bytes in 1 blocks.
==31221== possibly lost: 0 bytes in 0 blocks.
==31221== still reachable: 0 bytes in 0 blocks.
==31221== suppressed: 0 bytes in 0 blocks.
.
Liczba ==31221== to PID (Process ID) naszego procesu. Na samej górze komunikatu mamy dokładnie wyośloną nazwę modułu, nazwę funkcji i numer linijki powiązanej z naszym wyciekiem pamięci. Czyli wszystko, czego nam trzeba do naprawy błędu :)
Widzimy też, że wyciek zapoczątkowała funkcja main, która wywołała funkcję func, która wywołała operator new[ ].
Piękno narzędzia memcheck polega na tym, że dokładnie wskaże nam źródło problemu z pamięcią, nieważne jak głęboko w kodzie jest ono ukryte! Prawdziwy rocket launcher w walce z błędami pamięci.
Valgrind
Valgrind to cały zestaw narzędzi do profilowania i debugowania aplikacji wraz ze wszystkimi podpiętymi bibliotekami dynamicznymi. Dzięki niemu możesz zwiększyć szybkość działania i zmniejszyć ilość błędów w swoim programie. A jak on działa? Emuluje normalny procesor x86 i bacznie przygląda się temu, co program wyczynia…
Cały pakiet jest standardowo dostarczany z dystrybucjami Linuxa, wiec jeśli masz Linucha – najpewniej masz również Valgrinda gotowego do działania. Zeby to sprawdzić – po prostu wpisz z terminala polecenie: valgrind –version.
W skład pakietu wchodzi kilka narzędzi: do walki z problemami z pamięcią, z problemami związanymi z wielowątkowością, do profilowania cache’u itp.
Dziś zajmiemy się narzędziem memcheck, które pozwala w łatwy sposób wykryć problemy z pamięcią dowolnego programu!
Memcheck
Dzięki temu narzędziu możemy wykryć:
- odczyt z niezainicjalizowanej pamięci
- odczyt/zapis do pamięci, która została uprzednio zwolniona
- odczyt/zapis poza zaalokowanym blokiem pamięci
- odczyt/zapis do nieprawidłowych obszarów stosu
- nieprawidłowe łączenie funkcji typu malloc–delete oraz new–free
- nakładanie się obszarów pamięci w funkcji memcpy
- podwójne zwalnianie pamięci
- i wreszcie – wycieki pamięci
Zeby skorzystać z Valgrinda trzeba skompilować program np. kompilatorem gcc z flagami -g -O0, czyli z dołączeniem symboli do debugowania i wyłączoną optymalizacją. Następnie odpalamy program pod kontrolą memcheck’a:
> valgrind --tool=memcheck --leak-check=full ./program
Tak naprawdę kompilowanie w trybie debugowania wcale nie jest konieczne – Valgrind poradzi sobie z dowolnym, już skompilowanym programem. Wersja debugowalna dostarczy nam jednak więcej informacji w razie wykrycia błędów – np. numer linijki kodu, a wyłączenie optymalizacji pozwoli uniknąć błędnych wyników.
Przydatne flagi Valgrinda i memchecka:
- -v – tryb rozwlekły drukuje znacznie więcej informacji
- –help – opis podstawowych opcji Valgrida
- –leak-check=full – włącza sprawdzania wycieków pamięci (standardowo wyłączone)
- –leak-resolution=high – nie łączy raportów o kilku wyciekach w jeden wyciek. Stosuj z opcją wyżej
- –num-callers=10 – “błąd pamięci w funkcji A, którą wywołała funkcja B, którą wywołała funkcja C, którą wywołała …” i tak do dziesięciu wywołań w głąb, zamiast standardowych max czterech
- –log-file=raport.txt – informacje będą zapisywane do pliku a nie na standardowe wyjście. Do nazwy pliku zostanie dodany PID, a więc ostatecznie plik będzie wyglądał np. tak: raport.txt.31030
- więcej opcji memcheck’a znajdziesz tutaj: http://valgrind.org/docs/manual/mc-manual.html
Nareszcie!
Koniec teorii. Następnym razem pokażę Ci kilka przykładowych błędów, jakie memcheck może wychwycić, oraz jak są one raportowane.