Wszystko szczelne, czyli wykrywamy wycieki do pamięci w aplikacji C++ w systemach Microsoft Windows
Praca z językiem C/C++ daje spore możliwości w kwestii alokowania pamięci. Niestety, jak to zawsze w życiu bywa, w parze z dużą władzą idzie również spora odpowiedzialność. Najlepszą praktyką unikania błędów jest pisanie kodu, który ich nie zawiera – oczywiście jest to czysty truizm gdyż nie myli się ten kto nic nie robi. Pozostaje więc pytanie: jak żyć z alokowaniem pamięci na stercie?
W systemach GNU/Linuks programista ma do swojej dyspozycji wygodne i popularne narzędzie: Valgrind. Aplikacja ta doczekała się bezpośredniej integracji z QtCreatorem przez co atrakcyjność tego rozwiązania jest, w moich oczach, dość wysoka. Problem pojawia się gdy chcemy wykrywać przecieki do pamięci, na systemie firmy Microsoft. Valgrind jest niestety, projektem na wyłączność dla środowisk Uniksowych. W tym miejscu jako pierwsze rozwiązanie można by zaproponować: użycie Visual Studio z dedykowanym kompilatorem C++ i narzędziami do analizy pamięci. Wiąże się jednak to z pewnymi niedogodnościami. Osobiście preferuje QtCreator ze względu na jego niską konsumpcje pamięci operacyjnej w stosunku do IDE Microsoftu. Oczywiście, z każdą wersją kompilatora, Microsoft sprawa że jego narzędzie jest doskonalsze aczkolwiek niesmak wywołała u mnie informacja że dopiero w wersji 2017, kompilator Visual C++ doczekał się pełnego wsparcia dla standardu C++11/14. Dlatego mimowolnie korzystam z MinGW. Pozostaje więc pytanie, dlaczego nie pozostać przy pracy w środowisku Linuksowym? Odpowiedź jest bardzo prosta: system zarządzania zależnościami wersji bibliotek na pingwinie jest równie ułomny co wsparcie wcześniejszych wersji kompilatora Microsoftu dla standardu C++11/14. Nie kupuje konceptu bibliotek współdzielonych i tyle. Niby można z tym żyć, kombinować ze ściezkami bibliotek itp. ale wydaje mi się że warto poszukać lepszego rozwiązania.
We wcześniejszym akapicie nakreśliłem motywy jakimi kierowałem się przy wyborze narzędzi. Bez odpowiedzi pozostaje w dalszym ciągu pytanie, jak tropić wycieki do pamięci debugując aplikację na Windowsie, wykorzystując do pracy QtCreator/MinGW? Rozwiązaniem naszych problemów będzie Dr.Memory. Jest to w dużym uproszczeniu debuger pamięci dostępny na wszystkich platformach. Na stronie projektu możemy znaleźć wykres dotyczący zysku wydajności jaki przynosi nam korzystanie z Dr.Memory w stosunku do wcześniej wspomnianego Valgrinda. Osobiście nie przeprowadzałem żadnych pomiarów przez co zostaje mi wierzyć (ze stosowną dozą sceptycyzmu) że faktycznie tak jest.
Przykład
Jako że pewne rzeczy najłatwiej zrozumieć na prostym przykładzie, załóżmy że mamy taki kod:
struct PersonInfo { int age; std::string name; std::string secondName; }; void createMemoryLeak() { PersonInfo* newPerson = new PersonInfo(); } int main(int argc, char *argv[]) { createMemoryLeak(); createMemoryLeak(); createMemoryLeak(); return 0; }
Oczywiście celowo popełniono w nim zbrodnie nie zawołania operatora delete dla obiektu newPerson. Aby z korzystać z pełni dobrodziejstw programu Dr.Memory, naszą aplikację powinniśmy zbudować w trybie Debug. Mając zbudowany przykład mamy dwie możliwości, pierwsza to przeciągnąć plik wykonywalny .exe na ikonkę Dr.Memory jaka pojawiła się na pulpicie po instalacji aplikacji lub też otworzyć wiersz poleceń i przejść do katalogu gdzie mieści się nasz plik wykonywalny. Proponowałbym zastosować tą drugą opcje ze względu na to że będziemy mieli łatwiejszy dostęp do stosowania flag programu. Będąc w katalogu z naszym plikiem wykonywalnym, wpisujemy do wiersza poleceń komendę:
drmemory.exe ./MemTest.exe
I viola. Stała się magia. Aplikacja domyślnie przy instalacji, "dodaje się" do zmiennych środowiskowych systemu przez co problemów z wywołaniem z terminala, nie powinno być problemów. Przykładowy zrzut ekranu prezentuje poniżej działanie debugera pamięci.
Program wyrzucił nam zarówno w konsoli jak i w postaci pliku tekstowego, raport o tym jakie błędy znalazł. Tak samo jak w przypadku Valgrinda, mamy do dyspozycji informację w których miejscach naszego kodu wystąpił potencjalny problem.
Słowem podsumwania
Wpis był co prawda krótki aczkolwiek myślę że poruszał dość istotną kwestię. Bolączką rozwiązania jest fakt że nie istnieje żadna integracja z wykorzystywanym przeze mnie IDE. W ogólnym rozrachunku, korzystanie z Dr.Memory jest sytuacją Win‑Win. Nie uzależniamy się od żadnego systemu operacyjnego czy też kompilatora, dzięki czemu możemy elastycznie krążyć między platformami z zachowaniem pewności że na każdej z nich znajdziemy narzędzie które ułatwi naszą pracę.
Zachęcam do przetestowania samemu powyżej przedstawionego rozwiązania.