Znajdowanie przecieków pamięci za pomocą biblioteki CRT
Przecieki pamięci należą do najbardziej subtelnych i trudnych do wykrycia błędów w aplikacjach C/C++. Przecieki pamięci wynikają z niepowodzenia poprawnego cofnięcia przydziału pamięci, która została wcześniej przydzielona. Na początku może nie zostać zauważony mały przeciek pamięci, ale z upływem czasu może powodować objawy, od niskiej wydajności do awarii, gdy aplikacja zabraknie pamięci. Wyciek aplikacji, która używa całej dostępnej pamięci, może spowodować awarię innych aplikacji, co powoduje zamieszanie co do tego, która aplikacja jest odpowiedzialna. Nawet nieszkodliwe przecieki pamięci mogą wskazywać na inne problemy, które należy rozwiązać.
Debuger programu Visual Studio i biblioteka czasu wykonywania języka C (CRT) mogą pomóc w wykrywaniu i identyfikowaniu przecieków pamięci.
Włączanie wykrywania przecieków pamięci
Podstawowe narzędzia do wykrywania przecieków pamięci to debuger C/C++ i funkcje sterty debugowania CRT.
Aby włączyć wszystkie funkcje stert debugowania, dołącz następujące instrukcje w programie C++, w następującej kolejności:
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
Instrukcja #define
mapuje podstawową wersję funkcji sterty CRT na odpowiednią wersję debugowania. Jeśli pominiesz instrukcję #define
, zrzut wycieku pamięci będzie mniej szczegółowy.
Dołączenie pliku crtdbg.h mapuje malloc
funkcje i free
na ich wersje debugowania oraz _malloc_dbg
_free_dbg
, które śledzą alokację pamięci i cofanie alokacji. To mapowanie odbywa się tylko w kompilacjach debugowania, które mają wartość _DEBUG
. Kompilacje wydania używają zwykłych malloc
funkcji i free
.
Po włączeniu funkcji stert debugowania przy użyciu powyższych instrukcji umieść wywołanie _CrtDumpMemoryLeaks
przed punktem wyjścia aplikacji, aby wyświetlić raport przecieku pamięci po zakończeniu działania aplikacji.
_CrtDumpMemoryLeaks();
Jeśli aplikacja ma kilka zakończeń, nie musisz ręcznie umieszczać _CrtDumpMemoryLeaks
w każdym punkcie wyjścia. Aby wywołać _CrtDumpMemoryLeaks
wywołanie automatyczne do każdego punktu wyjścia, umieść wywołanie na _CrtSetDbgFlag
początku aplikacji przy użyciu pól bitowych pokazanych tutaj:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
Domyślnie _CrtDumpMemoryLeaks
generuje raport dotyczący przecieku pamięci do okienka Debugowanie w oknie Dane wyjściowe. Jeśli używasz biblioteki, biblioteka może zresetować dane wyjściowe do innej lokalizacji.
Możesz użyć _CrtSetReportMode
polecenia , aby przekierować raport do innej lokalizacji lub wrócić do okna Dane wyjściowe , jak pokazano poniżej:
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );
W poniższym przykładzie przedstawiono prosty wyciek pamięci i wyświetla informacje o wycieku pamięci przy użyciu polecenia _CrtDumpMemoryLeaks();
.
// debug_malloc.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_malloc.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
int* x = (int*)malloc(sizeof(int));
*x = 7;
printf("%d\n", *x);
x = (int*)calloc(3, sizeof(int));
x[0] = 7;
x[1] = 77;
x[2] = 777;
printf("%d %d %d\n", x[0], x[1], x[2]);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
_CrtDumpMemoryLeaks();
}
Interpretowanie raportu przecieku pamięci
Jeśli aplikacja nie definiuje _CRTDBG_MAP_ALLOC
elementu , _CrtDumpMemoryLeaks wyświetla raport dotyczący przecieku pamięci, który wygląda następująco:
Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
Jeśli aplikacja definiuje _CRTDBG_MAP_ALLOC
element , raport przecieku pamięci wygląda następująco:
Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
Drugi raport przedstawia nazwę pliku i numer wiersza, w którym po raz pierwszy przydzielono wyciek pamięci.
Niezależnie od tego, czy zdefiniujesz _CRTDBG_MAP_ALLOC
polecenie , zostanie wyświetlony raport dotyczący przecieku pamięci:
- Numer alokacji pamięci, który znajduje się
18
w przykładzie - Typ
normal
bloku w przykładzie. - W przykładzie lokalizacja
0x00780E80
pamięci szesnastkowej. - Rozmiar bloku
64 bytes
w przykładzie. - Pierwsze 16 bajtów danych w bloku w postaci szesnastkowej.
Typy bloków pamięci są normalne, klienckie lub CRT. Normalny blok to zwykła pamięć przydzielona przez program. Blok klienta to specjalny typ bloku pamięci używanego przez programy MFC dla obiektów, które wymagają destruktora. Operator MFC new
tworzy normalny blok lub blok klienta, odpowiednio do tworzonego obiektu.
Blok CRT jest przydzielany przez bibliotekę CRT do użytku własnego. Biblioteka CRT obsługuje cofnięcie przydziału dla tych bloków, więc bloki CRT nie będą wyświetlane w raporcie przecieku pamięci, chyba że występują poważne problemy z biblioteką CRT.
Istnieją dwa inne typy bloków pamięci, które nigdy nie pojawiają się w raportach przecieków pamięci. Wolny blok to pamięć, która została zwolniona, więc z definicji nie wycieka. Blok ignoruj to pamięć, którą jawnie oznaczono do wykluczenia z raportu przecieku pamięci.
Powyższe techniki identyfikują przecieki pamięci dla pamięci przydzielonej przy użyciu standardowej funkcji CRT malloc
. Jeśli jednak program przydziela pamięć przy użyciu operatora języka C++ new
, może być widoczny tylko nazwa pliku i numer wiersza, w którym operator new
wywołania są wywoływane _malloc_dbg
w raporcie dotyczącym przecieku pamięci. Aby utworzyć bardziej przydatny raport dotyczący przecieku pamięci, możesz napisać makro podobne do poniższego, aby zgłosić wiersz, który dokonał alokacji:
#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#else
#define DBG_NEW new
#endif
Teraz możesz zastąpić new
operator za pomocą DBG_NEW
makra w kodzie. W kompilacjach debugowania używa przeciążenia globalnegooperator new
, DBG_NEW
które przyjmuje dodatkowe parametry dla typu bloku, pliku i numeru wiersza. Przeciążenie wywołań _malloc_dbg
do rejestrowania new
dodatkowych informacji. Raporty dotyczące wycieku pamięci pokazują nazwę pliku i numer wiersza, w którym przydzielono wyciekły obiekty. Kompilacje wersji nadal używają domyślnego new
elementu . Oto przykład techniki:
// debug_new.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_new.cpp
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#else
#define DBG_NEW new
#endif
struct Pod {
int x;
};
int main() {
Pod* pPod = DBG_NEW Pod;
pPod = DBG_NEW Pod; // Oops, leaked the original pPod!
delete pPod;
_CrtDumpMemoryLeaks();
}
Po uruchomieniu tego kodu w debugerze programu Visual Studio wywołanie w celu _CrtDumpMemoryLeaks
wygenerowania raportu w oknie Dane wyjściowe wygląda następująco:
Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\debug_new\debug_new.cpp(20) : {75}
normal block at 0x0098B8C8, 4 bytes long.
Data: < > CD CD CD CD
Object dump complete.
Te dane wyjściowe zgłaszają, że wyciekła alokacja w wierszu 20 debug_new.cpp.
Uwaga
Nie zalecamy tworzenia makra preprocesora o nazwie new
lub innego słowa kluczowego języka.
Ustawianie punktów przerwania dla numeru alokacji pamięci
Numer alokacji pamięci informuje o przydzieleniu wycieku bloku pamięci. Blok z liczbą alokacji pamięci 18, na przykład, to 18 blok pamięci przydzielony podczas uruchamiania aplikacji. Raport CRT zlicza wszystkie alokacje bloków pamięci podczas przebiegu, w tym alokacje biblioteki CRT i innych bibliotek, takich jak MFC. W związku z tym blok alokacji pamięci numer 18 prawdopodobnie nie jest 18 blokiem pamięci przydzielonym przez kod.
Możesz użyć numeru alokacji, aby ustawić punkt przerwania na alokacji pamięci.
Aby ustawić punkt przerwania alokacji pamięci przy użyciu okna Czujka
Ustaw punkt przerwania w pobliżu początku aplikacji i rozpocznij debugowanie.
Gdy aplikacja wstrzymuje się w punkcie przerwania, otwórz okno Obserwowanie, wybierając pozycję Debuguj>usługę Windows>Watch 1 (lub Watch 2, Watch 3 lub Watch 4).
W oknie Obserwowanie wpisz
_crtBreakAlloc
kolumnę Nazwa.Jeśli używasz wielowątkowej wersji biblioteki DLL biblioteki CRT (opcja /MD), dodaj operator kontekstu:
{,,ucrtbased.dll}_crtBreakAlloc
Upewnij się, że symbole debugowania zostały załadowane.
_crtBreakAlloc
W przeciwnym razie jest zgłaszany jako niezidentyfikowany.Naciśnij klawisz Enter.
Debuger ocenia wywołanie i umieszcza wynik w kolumnie Wartość . Ta wartość to -1 , jeśli nie ustawiono żadnych punktów przerwania dla alokacji pamięci.
W kolumnie Wartość zastąp wartość liczbą alokacji alokacji pamięci, w której ma zostać przerwany debuger.
Po ustawieniu punktu przerwania na numerze alokacji pamięci przejdź do debugowania. Upewnij się, że działa w tych samych warunkach, więc numer alokacji pamięci nie zmienia się. Gdy program przerywa określoną alokację pamięci, użyj okna stosu wywołań i innych okien debugera, aby określić warunki, w których przydzielono pamięć. Następnie można kontynuować wykonywanie, aby obserwować, co się dzieje z obiektem i określić, dlaczego nie jest poprawnie cofnięty przydział.
Ustawienie punktu przerwania danych na obiekcie może być również przydatne. Aby uzyskać więcej informacji, zobacz Używanie punktów przerwania.
Punkty przerwania alokacji pamięci można również ustawić w kodzie. Można ustawić:
_crtBreakAlloc = 18;
or:
_CrtSetBreakAlloc(18);
Porównanie stanów pamięci
Inną techniką lokalizowania przecieków pamięci jest tworzenie migawek stanu pamięci aplikacji w kluczowych punktach. Aby utworzyć migawkę stanu pamięci w danym punkcie aplikacji, utwórz _CrtMemState
strukturę i przekaż ją do _CrtMemCheckpoint
funkcji.
_CrtMemState s1;
_CrtMemCheckpoint( &s1 );
Funkcja _CrtMemCheckpoint
wypełnia strukturę migawką bieżącego stanu pamięci.
Aby wyświetlić zawartość _CrtMemState
struktury, przekaż strukturę do _ CrtMemDumpStatistics
funkcji:
_CrtMemDumpStatistics( &s1 );
_CrtMemDumpStatistics
generuje zrzut stanu pamięci, który wygląda następująco:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.
Aby określić, czy wyciek pamięci wystąpił w sekcji kodu, możesz wykonać migawki stanu pamięci przed sekcją i po nim, a następnie użyć polecenia _CrtMemDifference
, aby porównać dwa stany:
_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );
if ( _CrtMemDifference( &s3, &s1, &s2) )
_CrtMemDumpStatistics( &s3 );
_CrtMemDifference
porównuje stany s1
pamięci i s2
zwraca wynik (s3
), który jest różnicą między s1
i s2
.
Jedna technika znajdowania przecieków pamięci rozpoczyna się od umieszczania _CrtMemCheckpoint
wywołań na początku i na końcu aplikacji, a następnie przy użyciu funkcji _CrtMemDifference
porównywania wyników. Jeśli _CrtMemDifference
zostanie wyświetlony przeciek pamięci, możesz dodać więcej _CrtMemCheckpoint
wywołań, aby podzielić program przy użyciu wyszukiwania binarnego, dopóki nie odizolujesz źródła wycieku.
Wyniki fałszywie dodatnie
_CrtDumpMemoryLeaks
może dać fałszywe wskazania przecieków pamięci, jeśli biblioteka oznacza wewnętrzne alokacje jako normalne bloki zamiast bloków CRT lub bloków klienta. W takim przypadku _CrtDumpMemoryLeaks
nie można odróżnić alokacji użytkowników i alokacji bibliotek wewnętrznych. Jeśli globalne destruktory alokacji biblioteki są uruchamiane po punkcie, w którym jest wywoływana _CrtDumpMemoryLeaks
, każda wewnętrzna alokacja biblioteki jest zgłaszana jako przeciek pamięci. Wersje standardowej biblioteki szablonów starsze niż program Visual Studio .NET mogą powodować _CrtDumpMemoryLeaks
zgłaszanie takich wyników fałszywie dodatnich.