Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Jeśli debugujesz program MFC, te techniki debugowania mogą być przydatne.
AfxDebugBreak
MFC udostępnia specjalną funkcję AfxDebugBreak dla punktów przerwania kodowania na stałe w kodzie źródłowym:
AfxDebugBreak( );
Na platformach AfxDebugBreak Intel tworzy następujący kod, który łamie kod źródłowy, a nie kod jądra:
_asm int 3
Na innych platformach AfxDebugBreak tylko wywołuje metodę DebugBreak.
Pamiętaj, aby usunąć AfxDebugBreak instrukcje podczas tworzenia kompilacji wydania lub otoczyć je za pomocą #ifdef _DEBUG.
Makro TRACE
Aby wyświetlić komunikaty z programu w oknie dane wyjściowe debugera, możesz użyć makra ATLTRACE lub makra MFC TRACE . Podobnie jak asercji, makra śledzenia są aktywne tylko w wersji debugowania programu i znikają po skompilowaniu w wersji wydania.
W poniższych przykładach przedstawiono niektóre sposoby użycia makra TRACE . Podobnie jak printf, makro TRACE może obsługiwać wiele argumentów.
int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );
TRACE( "The value of x is %d\n", x );
TRACE( "x = %d and y = %d\n", x, y );
TRACE( "x = %d and y = %x and z = %f\n", x, y, z );
Makro TRACE odpowiednio obsługuje parametry char* i wchar_t*. W poniższych przykładach pokazano użycie makra TRACE wraz z różnymi typami parametrów ciągu.
TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);
TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);
TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);
Aby uzyskać więcej informacji na temat makra TRACE , zobacz Usługi diagnostyczne.
Wykrywanie przecieków pamięci w MFC
MFC udostępnia klasy i funkcje do wykrywania pamięci przydzielonej, ale nigdy nie zwolnionej.
Śledzenie alokacji pamięci
W MFC można użyć DEBUG_NEW makra zamiast nowego operatora, aby ułatwić lokalizowanie przecieków pamięci. W wersji debugowania programu DEBUG_NEW śledzi nazwę pliku i numer wiersza dla każdego przydzielanego obiektu. Podczas kompilowania wersji Release programu, DEBUG_NEW rozwiązuje się do prostej operacji new bez informacji o nazwie pliku i numerze wiersza. W związku z tym nie płacisz żadnej szybkiej kary w wersji wydania programu.
Jeśli nie chcesz przepisywać całego programu, aby używać DEBUG_NEW zamiast new, możesz zdefiniować to makro w plikach źródłowych:
#define new DEBUG_NEW
Podczas zrzutu obiektu każdy przydzielony DEBUG_NEW obiekt będzie wyświetlać plik i numer wiersza, w którym został przydzielony, co pozwala wskazać źródła przecieków pamięci.
Wersja Debug frameworku MFC używa DEBUG_NEW automatycznie, ale twój kod tego nie robi. Jeśli chcesz korzystać z DEBUG_NEW , musisz użyć DEBUG_NEW jawnie lub #define new, jak pokazano powyżej.
Włączanie diagnostyki pamięci
Przed rozpoczęciem korzystania z funkcji diagnostyki pamięci należy włączyć śledzenie diagnostyczne.
Aby włączyć lub wyłączyć diagnostykę pamięci
Wywołaj funkcję globalną AfxEnableMemoryTracking , aby włączyć lub wyłączyć alokator pamięci diagnostycznej. Ponieważ diagnostyka pamięci jest domyślnie włączona w bibliotece debugowania, zazwyczaj ta funkcja jest używana do tymczasowego wyłączania, co zwiększa szybkość wykonywania programu i zmniejsza liczbę danych wyjściowych diagnostyki.
Aby wybrać określone diagnostyczne funkcje pamięci przy użyciu afxMemDF
Jeśli chcesz dokładniej kontrolować funkcje diagnostyczne pamięci, możesz selektywnie włączyć i wyłączyć poszczególne funkcje diagnostyczne pamięci, ustawiając wartość zmiennej globalnej MFC afxMemDF. Ta zmienna może mieć następujące wartości określone przez wyliczony typ afxMemDF.
Wartość Opis allocMemDF powiedział: Włącz alokator pamięci diagnostycznej (ustawienie domyślne). delayFreeMemDF powiedział: Opóźnij zwalnianie pamięci przy wywoływaniu deletelubfreeaż do zakończenia działania programu. Spowoduje to przydzielenie maksymalnej możliwej ilości pamięci przez program.checkAlwaysMemDF powiedział: Wywołaj metodę AfxCheckMemory za każdym razem, gdy pamięć jest przydzielana lub zwalniana. Te wartości można używać w połączeniu, wykonując operację logiczną OR, jak pokazano poniżej:
afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
Tworzenie zrzutów pamięci
Utwórz obiekt CMemoryState i wywołaj funkcję elementu członkowskiego CMemoryState::Checkpoint . Spowoduje to utworzenie pierwszej migawki pamięci.
Po wykonaniu przez program operacji alokacji i zwalniania pamięci utwórz kolejny
CMemoryStateobiekt i wywołajCheckpointdla tego obiektu. To jest drugi momentalny obraz użycia pamięci.Utwórz trzeci
CMemoryStateobiekt i wywołaj jego funkcję składową CMemoryState::D ifference , podając jako argumenty dwóch poprzednichCMemoryStateobiektów. Jeśli istnieje różnica między dwoma stanami pamięci,Differencefunkcja zwraca wartość niezerową. Oznacza to, że niektóre bloki pamięci nie zostały zwolnione.W tym przykładzie pokazano, jak wygląda kod:
// Declare the variables needed #ifdef _DEBUG CMemoryState oldMemState, newMemState, diffMemState; oldMemState.Checkpoint(); #endif // Do your memory allocations and deallocations. CString s("This is a frame variable"); // The next object is a heap object. CPerson* p = new CPerson( "Smith", "Alan", "581-0215" ); #ifdef _DEBUG newMemState.Checkpoint(); if( diffMemState.Difference( oldMemState, newMemState ) ) { TRACE( "Memory leaked!\n" ); } #endifZwróć uwagę, że instrukcje sprawdzania pamięci są otoczone przez bloki #ifdef _DEBUG / #endif, aby były kompilowane tylko w wersjach Debug programu.
Teraz, gdy już wiesz, że istnieje przeciek pamięci, możesz użyć innej funkcji składowej CMemoryState::D umpStatistics , która pomoże Ci go zlokalizować.
Wyświetlanie statystyk pamięci
Funkcja CMemoryState::Difference analizuje dwa obiekty stanu pamięci i wykrywa wszystkie obiekty, które nie zostały zwolnione z sterty między stanami początkowymi i końcowymi. Po wykonaniu zrzutów pamięci i porównaniu ich przy użyciu CMemoryState::Difference polecenia, można wywołać funkcję CMemoryState::DumpStatistics, aby uzyskać informacje o obiektach, które nie zostały zdealokowane.
Rozważmy następujący przykład:
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpStatistics();
}
Przykładowy zrzut danych z przykładu wygląda następująco:
0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes
Bloki wolne to bloki, których dealokacja jest opóźniona, jeśli afxMemDF jest ustawione na delayFreeMemDF.
Zwykłe bloki obiektów, wyświetlane w drugim wierszu, pozostają przydzielone na stercie.
Bloki niezwiązane z obiektami obejmują tablice i struktury przydzielone za pomocą polecenia new. W tym przypadku na stercie przydzielono cztery bloki niezwiązane z obiektem, ale nie cofnięto przydziału.
Largest number used daje maksymalną ilość pamięci używanej przez program w dowolnym momencie.
Total allocations daje całkowitą ilość pamięci używanej przez program.
Pobieranie zrzutów obiektów
W programie MFC można użyć CMemoryState::DumpAllObjectsSince, aby wyświetlić opis wszystkich obiektów na stercie, które nie zostały dealokowane.
DumpAllObjectsSince zrzuca wszystkie obiekty przydzielone od ostatniego CMemoryState::Checkpoint. Jeśli nie miało miejsca żadne wywołanie Checkpoint, DumpAllObjectsSince zrzuca wszystkie obiekty i nieobiekty znajdujące się obecnie w pamięci.
Uwaga
Przed użyciem zrzutu obiektów MFC, należy włączyć śledzenie diagnostyczne.
Uwaga
MFC automatycznie zrzuca wszystkie przecieki obiektów po zakończeniu programu, więc nie musisz tworzyć kodu do zrzutu obiektów.
Poniższy kod testuje obecność wycieku pamięci, porównując dwa stany pamięci i zrzuca wszystkie obiekty, jeśli zostanie wykryty wyciek.
if( diffMemState.Difference( oldMemState, newMemState ) )
{
TRACE( "Memory leaked!\n" );
diffMemState.DumpAllObjectsSince();
}
Zawartość zrzutu wygląda następująco:
Dumping objects ->
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
Liczby w nawiasach klamrowych na początku większości wierszy określają kolejność przydzielania obiektów. Ostatnio przydzielony obiekt ma największą liczbę i pojawia się w górnej części zrzutu.
Aby uzyskać maksymalną ilość informacji z zrzutu obiektu, można zastąpić Dump funkcję składową dowolnego CObjectobiektu pochodnego w celu dostosowania zrzutu obiektu.
Punkt przerwania można ustawić dla określonej alokacji pamięci, ustawiając zmienną _afxBreakAlloc globalną na liczbę pokazaną w nawiasach klamrowych. Jeśli uruchomisz ponownie program, debuger przerwi wykonywanie po zakończeniu tej alokacji. Następnie możesz przyjrzeć się stosowi wywołań, aby zobaczyć, jak twój program dotarł do tego punktu.
Biblioteka uruchomieniowa C ma podobną funkcję, _CrtSetBreakAlloc, której można użyć do alokacji w bibliotece uruchomieniowej C.
Interpretowanie zrzutów pamięci
Przyjrzyj się zrzutowi tego obiektu bardziej szczegółowo:
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
{1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
Program, który wygenerował ten zrzut, miał tylko dwie jawne alokacje — jeden na stosie i jeden na stercie:
// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
Konstruktor CPerson przyjmuje trzy argumenty, które są wskaźnikami do char, które są używane do inicjowania CString zmiennych składowych. W zrzucie pamięci można zobaczyć CPerson obiekt wraz z trzema blokami nieobiektowymi (3, 4 i 5). Przechowują one znaki zmiennych CString składowych i nie zostaną usunięte po CPerson wywołaniu destruktora obiektu.
Blok numer 2 jest samym obiektem CPerson .
$51A4 reprezentuje adres bloku i następuje po nim zawartość obiektu, która była wyjściowa przez CPerson::Dump po wywołaniu przez DumpAllObjectsSince.
Można odgadnąć, że numer bloku 1 jest skojarzony ze zmienną ramki CString z powodu jego numeru sekwencyjnego i rozmiaru, który odpowiada liczbie znaków w zmiennej ramki CString. Zmienne przydzielone na ramce są automatycznie cofane, gdy ramka wykracza poza zakres.
Zmienne ramki
Ogólnie rzecz biorąc, nie należy martwić się o obiekty sterty skojarzone ze zmiennymi ramki, ponieważ są one automatycznie cofane, gdy zmienne ramki wykraczają poza zakres. Aby uniknąć bałaganu w zrzutach diagnostycznych pamięci, należy umieścić wywołania tak Checkpoint , aby były poza zakresem zmiennych ramek. Na przykład umieść nawiasy zakresu wokół poprzedniego kodu alokacji, jak pokazano poniżej:
oldMemState.Checkpoint();
{
// Do your memory allocations and deallocations ...
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
}
newMemState.Checkpoint();
Przy ustawionych nawiasach zakresu zrzut pamięci dla tego przykładu wygląda następująco:
Dumping objects ->
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
Alokacje nieprzedmiotowe
Zauważ, że niektóre alokacje są obiektami (takimi jak CPerson) i niektóre są alokacjami nieobiektowymi. "Alokacje nieobiektowe" to alokacje na obiekty, które nie są pochodnymi CObject, lub alokacje typów pierwotnych C, takich jak char, int lub long. Jeśli klasa pochodna CObject przydziela dodatkowe miejsce, na przykład dla wewnętrznych bufory, te obiekty będą pokazywać zarówno alokacje obiektów, jak i nieobiektów.
Zapobieganie wyciekom pamięci
Zwróć uwagę, że w powyższym kodzie blok pamięci skojarzony ze CString zmienną ramki został automatycznie cofnięty i nie jest wyświetlany jako przeciek pamięci. Automatyczna dealokacja skojarzona z regułami określania zakresu rozwiązuje większość problemów z przeciekami pamięci związanymi ze zmiennymi stosu.
W przypadku obiektów przydzielonych na stercie należy jednak jawnie usunąć obiekt, aby zapobiec wyciekowi pamięci. Aby wyczyścić ostatni wyciek pamięci w poprzednim przykładzie, usuń obiekt CPerson przydzielony na stercie w następujący sposób:
{
// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
delete p;
}
Dostosowywanie zrzutów obiektów
W przypadku dziedziczenia klasy od CObjecta, można zastąpić funkcję członka Dump, aby podać dodatkowe informacje, używając funkcji DumpAllObjectsSince do zrzucania obiektów do okna wyjściowego.
Funkcja Dump zapisuje tekstową reprezentację zmiennych składowych obiektu w kontekście zrzutu (CDumpContext). Kontekst zrzutu jest podobny do strumienia wejścia/wyjścia. Możesz użyć operatora dołączania (<<), aby wysłać dane do elementu CDumpContext.
Po zastąpieniu Dump funkcji należy najpierw wywołać wersję Dump klasy bazowej, aby zrzucić zawartość obiektu klasy bazowej. Następnie wyprowadź tekstowy opis i wartość dla każdej zmiennej składowej klasy pochodnej.
Deklaracja Dump funkcji wygląda następująco:
class CPerson : public CObject
{
public:
#ifdef _DEBUG
virtual void Dump( CDumpContext& dc ) const;
#endif
CString m_firstName;
CString m_lastName;
// And so on...
};
Ponieważ zrzucenie obiektu ma sens tylko podczas debugowania programu, deklaracja funkcji Dump jest zawarta w bloku #ifdef _DEBUG / #endif.
W poniższym przykładzie Dump funkcja najpierw wywołuje funkcję dla swojej klasy bazowej Dump . Następnie zapisuje krótki opis każdej zmiennej składowej wraz z jej wartością do strumienia diagnostycznego.
#ifdef _DEBUG
void CPerson::Dump( CDumpContext& dc ) const
{
// Call the base class function first.
CObject::Dump( dc );
// Now do the stuff for our specific class.
dc << "last name: " << m_lastName << "\n"
<< "first name: " << m_firstName << "\n";
}
#endif
Musisz podać argument CDumpContext, aby określić miejsce, do którego trafią dane wyjściowe zrzutu. Wersja debugowania MFC dostarcza wstępnie zdefiniowany CDumpContext obiekt o nazwie afxDump , który wysyła dane wyjściowe do debugera.
CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif
Zmniejszenie rozmiaru kompilacji debugowania MFC
Informacje debugowania dla dużej aplikacji MFC mogą zająć dużo miejsca na dysku. Aby zmniejszyć rozmiar, można użyć jednej z tych procedur:
Skompiluj biblioteki MFC przy użyciu opcji /Z7, /Zi, /ZI (Format informacji debugowania) zamiast /Z7. Te opcje tworzą pojedynczy plik bazy danych programu (PDB), który zawiera informacje debugowania dla całej biblioteki, zmniejszając nadmiarowość i oszczędność miejsca.
Ponownie skompiluj biblioteki MFC bez informacji debugowania (brak opcji /Z7, /Zi, /ZI (Format informacji debugowania). W takim przypadku brak informacji o debugowaniu uniemożliwi korzystanie z większości obiektów debugera w kodzie biblioteki MFC, ale ponieważ biblioteki MFC są już dokładnie debugowane, może to nie być problem.
Utwórz własną aplikację przy użyciu informacji debugowania dla wybranych modułów tylko zgodnie z poniższym opisem.
Kompilowanie aplikacji MFC z informacjami o debugowaniu dla wybranych modułów
Kompilowanie wybranych modułów za pomocą bibliotek debugowania MFC umożliwia korzystanie z kroków i innych obiektów debugowania w tych modułach. Ta procedura wykorzystuje zarówno konfiguracje Debug, jak i Release projektu, co wymaga wprowadzenia zmian opisanych w poniższych krokach (a także konieczne jest wykonanie "zbuduj ponownie wszystko", gdy wymagana jest pełna kompilacja Release).
W Eksplorator rozwiązań wybierz projekt.
Z menu Widok wybierz pozycję Strony właściwości.
Najpierw utworzysz nową konfigurację projektu.
W oknie dialogowym <Strony właściwości projektu> kliknij przycisk Menadżer konfiguracji.
W oknie dialogowym Configuration Manager znajdź projekt w siatce. W kolumnie Konfiguracja wybierz pozycję <Nowy...>.
W oknie dialogowym Nowa konfiguracja projektu wpisz nazwę nowej konfiguracji, taką jak "Debugowanie częściowe", w polu Nazwa konfiguracji projektu .
Z listy Kopiuj ustawienia wybierz pozycję Wydanie.
Kliknij przycisk OK , aby zamknąć okno dialogowe Nowa konfiguracja projektu .
Zamknij okno dialogowe Configuration Manager .
Teraz ustawisz opcje dla całego projektu.
W oknie dialogowym Strony właściwości w folderze Właściwości konfiguracji wybierz kategorię Ogólne .
W tabeli ustawień projektu rozwiń Ustawienia domyślne projektu (jeśli to konieczne).
W obszarze Project Defaults (Wartości domyślne projektu) znajdź pozycję Use of MFC (Użycie MFC). Bieżące ustawienie jest wyświetlane w prawej kolumnie siatki. Kliknij bieżące ustawienie i zmień je na Use MFC in a Static Library (Używanie MFC w bibliotece statycznej).
W lewym okienku okna dialogowego Strony właściwości otwórz folder C/C++ i wybierz pozycję Preprocesor. W siatce właściwości znajdź definicje preprocesora i zastąp ciąg "NDEBUG" ciągiem "_DEBUG".
W lewym okienku okna dialogowego Strony właściwości otwórz folder Linker i wybierz kategorię Wejście. W siatce właściwości znajdź pozycję Dodatkowe zależności. W ustawieniu Dodatkowe zależności wpisz "NAFXCWD. LIB" i "LIBCMT".
Kliknij przycisk OK , aby zapisać nowe opcje kompilacji i zamknąć okno dialogowe Strony właściwości .
W menu Kompilacja wybierz pozycję Skompiluj. Spowoduje to usunięcie wszystkich informacji debugowania z modułów, ale nie ma wpływu na bibliotekę MFC.
Teraz musisz dodać informacje debugowania z powrotem do wybranych modułów w aplikacji. Pamiętaj, że można ustawić punkty przerwania i wykonywać inne funkcje debugera tylko w modułach skompilowanych przy użyciu informacji o debugowaniu. Dla każdego pliku projektu, w którym chcesz uwzględnić informacje debugowania, wykonaj następujące kroki:
W Eksploratorze rozwiązań otwórz folder Pliki źródłowe znajdujące się w projekcie.
Wybierz plik, dla którego chcesz ustawić informacje debugowania.
Z menu Widok wybierz pozycję Strony właściwości.
W oknie dialogowym Strony właściwości w folderze Ustawienia konfiguracji otwórz folder C/C++ , a następnie wybierz kategorię Ogólne .
W siatce właściwości znajdź pozycję Format informacji debugowania.
Kliknij ustawienia Format informacji debugowania i wybierz odpowiednią opcję (zazwyczaj /ZI), aby uzyskać informacje o debugowaniu.
Jeśli używasz aplikacji wygenerowanej przez kreatora lub masz wstępnie skompilowane nagłówki, musisz wyłączyć wstępnie skompilowane nagłówki lub ponownie skompilować je przed skompilowaniem innych modułów. W przeciwnym razie zostanie wyświetlone ostrzeżenie C4650 i komunikat o błędzie C2855. Prekompilowane nagłówki można wyłączyć, zmieniając ustawienie Utwórz/Użyj prekompilowanych nagłówków w <oknie dialogowym Właściwości projektu> (folder Właściwości konfiguracji, podfolder C/C++, kategoria Prekompilowane nagłówki).
W menu Kompilacja wybierz pozycję Kompiluj, aby ponownie skompilować pliki projektu, które są nieaktualne.
Alternatywą dla techniki opisanej w tym temacie jest użycie zewnętrznego pliku makefile do zdefiniowania poszczególnych opcji dla każdego pliku. W takim przypadku, aby połączyć się z bibliotekami debugowania MFC, należy zdefiniować flagę _DEBUG dla każdego modułu. Jeśli chcesz używać bibliotek wydania MFC, musisz zdefiniować usługę NDEBUG. Aby uzyskać więcej informacji na temat pisania zewnętrznych plików make, zobacz dokumentację narzędzia NMAKE.