3.6 — Korzystanie ze zintegrowanego debugera: Stepping

Po uruchomieniu programu wykonanie rozpoczyna się na górze funkcji głównego , a następnie przebiega sekwencyjnie z instrukcjami instrukcję, aż do zakończenia programu. W dowolnym momencie działania programu program śledzi wiele rzeczy: wartość używanych zmiennych, jakie funkcje zostały wywołane (aby po powrocie tych funkcji program wiedział, dokąd wrócić) oraz bieżący punkt wykonania w programie (więc wiedział, którą instrukcję wykonać dalej). Wszystkie te śledzone informacje nazywane są stanu programu (lub po prostu stan).

W poprzednich lekcjach badaliśmy różne sposoby modyfikowania kodu, aby ułatwić debugowanie, w tym drukowanie informacji diagnostycznych lub używanie rejestratora. Są to proste metody sprawdzania stanu programu podczas jego działania. Chociaż mogą one być skuteczne, jeśli są właściwie stosowane, nadal mają wady: wymagają zmiany kodu, co zajmuje czas i może wprowadzić nowe błędy, a także zaśmiecają kod, czyniąc istniejący kod trudniejszym do zrozumienia.

Za technikami, które pokazaliśmy do tej pory, kryje się nieokreślone założenie: że po uruchomieniu kodu zostanie on ukończony (tylko z pauzą w celu zaakceptowania wprowadzonych danych) bez możliwości interwencji i sprawdzenia wyników programu w dowolnym momencie chcesz.

A gdybyśmy jednak mogli usunąć to założenie? Na szczęście większość nowoczesnych IDE jest wyposażona w zintegrowane narzędzie zwane debugerem, które zostało zaprojektowane dokładnie w tym celu.

Debugger

A debugera to program komputerowy, który umożliwia programiście kontrolowanie sposobu wykonywania innego programu i sprawdzanie stanu programu podczas jego działania. Na przykład programista może użyć debugera do wykonania programu linia po linii, sprawdzając po drodze wartość zmiennych. Porównując rzeczywistą wartość zmiennych z oczekiwaną lub obserwując ścieżkę wykonania kodu, debugger może ogromnie pomóc w wyśledzeniu błędów semantycznych (logicznych).

Moc stojąca za debugerem jest dwojaka: możliwość precyzyjnego kontrolowania wykonywania programu oraz możliwość przeglądania (i modyfikowania, jeśli to konieczne) stanu programu.

Początkowo debugery (takie jak as gdb) były oddzielnymi programami wyposażonymi w interfejs wiersza poleceń, w którym programista musiał wpisywać tajemnicze polecenia, aby zaczęły działać. Późniejsze debugery (takie jak wczesne wersje turbodebugera firmy Borland) były nadal oddzielnymi programami, ale zapewniały „graficzny” interfejs użytkownika, ułatwiający pracę z nimi. Obecnie wiele nowoczesnych IDE ma zintegrowany debugger -- czyli debugger korzystający z tego samego interfejsu co edytor kodu, dzięki czemu można debugować przy użyciu tego samego środowiska, w którym piszesz kod (zamiast konieczności przełączania programów).

Chociaż zintegrowane debugery są bardzo wygodne i zalecane dla początkujących, debugery wiersza poleceń są dobrze obsługiwane i nadal powszechnie używane w środowiskach, które nie obsługują interfejsów graficznych (np. wbudowanych). systemy).

Prawie wszystkie nowoczesne debugery zawierają ten sam standardowy zestaw podstawowych funkcji — jednakże istnieje niewielka spójność w zakresie układu menu umożliwiających dostęp do tych funkcji, a jeszcze mniej spójności w skrótach klawiaturowych. Chociaż w naszych przykładach będą używane zrzuty ekranu z Microsoft Visual Studio (omówimy także, jak zrobić wszystko w Code::Blocks), nie powinieneś mieć problemów ze znalezieniem dostępu do każdej omawianej przez nas funkcji, niezależnie od tego, jakiego IDE używasz.

Wskazówka

Skróty klawiaturowe debugera będą działać tylko wtedy, gdy aktywnym oknem jest IDE/zintegrowany debuger.

Pozostała część tego rozdziału zostanie poświęcona nauce korzystania z debugera.

Wskazówka

Nie zaniedbuj nauki korzystania z debugera. W miarę jak programy stają się coraz bardziej skomplikowane, czas poświęcony na naukę efektywnego korzystania ze zintegrowanego debugera będzie blady w porównaniu z czasem zaoszczędzonym na znajdowaniu i naprawianiu problemów.

Ostrzeżenie

Przed kontynuowaniem tej lekcji (i kolejnych lekcji związanych z używaniem debugera) upewnij się, że projekt jest skompilowany przy użyciu konfiguracji kompilacji debugowania (więcej informacji można znaleźć w 0.9 — Konfigurowanie kompilatora: Konfiguracje kompilacji ).

Jeśli kompilujesz projekt przy użyciu konfiguracji wydania, funkcjonalność debuger może nie działać poprawnie (np. gdy spróbujesz wejść do programu, zamiast tego po prostu go uruchomi).

W przypadku użytkowników Code::Blocks

Jeśli używasz Code::Blocks, twój debuger może być poprawnie skonfigurowany lub nie. Sprawdźmy.

Najpierw przejdź do menu Ustawienia > Debuger…. Następnie otwórz drzewo Debugger GDB/CDB po lewej stronie i wybierz Domyślny. Powinno otworzyć się okno dialogowe wyglądające mniej więcej tak:

Jeśli widzisz duży czerwony pasek w miejscu, w którym powinna znajdować się „Ścieżka pliku wykonywalnego”, oznacza to, że musisz zlokalizować swój debuger. W tym celu należy kliknąć przycisk po prawej stronie pola Ścieżka pliku wykonywalnego . Następnie znajdź w swoim systemie plik „gdb32.exe” — mój znajdował się w C:\Program Files (x86)\CodeBlocks\MinGW\bin\gdb32.exe. Następnie kliknij OK.

W przypadku użytkowników Code::Blocks

Istnieją doniesienia, że ​​zintegrowany debuger Code::Blocks (GDB) może mieć problemy z rozpoznawaniem niektórych ścieżek plików zawierających spacje lub znaki inne niż angielskie. Jeśli w trakcie tych lekcji debuger wydaje się działać nieprawidłowo, może to być powód.

Dla użytkowników VS Code

Aby skonfigurować debugowanie, naciśnij Ctrl+Shift+P i wybierz „C/C++: Dodaj konfigurację debugowania”, a następnie „C/C++: g++ kompilacja i debugowanie aktywnego pliku”. Powinno to spowodować utworzenie i otwarcie pliku konfiguracyjnego launch.json . Zmień „stopAtEntry” na true:
"stopAtEntry": true,

Następnie otwórz main.cpp i rozpocznij debugowanie, naciskając F5 lub naciskając Ctrl+Shift+P i wybierając „Debugowanie: rozpocznij debugowanie i zatrzymaj przy wejściu”.

Stepping

Naszą eksplorację debugera rozpoczniemy od najpierw przyjrzymy się niektórym narzędziom do debugowania, które pozwalają nam kontrolować sposób wykonywania programu.

Stepping to nazwa zestawu powiązanych funkcji debugera, które pozwalają nam wykonywać (przechodzić) nasz kod instrukcja po instrukcji.

Istnieje wiele powiązanych poleceń krokowych, które omówimy po kolei.

Krok w

Klasa step in polecenie wykonuje następną instrukcję w normalnej ścieżce wykonywania programu, a następnie wstrzymuje wykonywanie programu, aby tak stan programu możemy sprawdzić za pomocą debugera. Jeżeli wykonywana instrukcja zawiera wywołanie funkcji, step in powoduje, że program przeskoczy na początek wywoływanej funkcji, gdzie się zatrzyma.

Przyjrzyjmy się bardzo prostemu programowi:

#include <iostream>

void printValue(int value)
{
    std::cout << value << '\n';
}

int main()
{
    printValue(5);

    return 0;
}

Debugujmy ten program za pomocą polecenia step in .

Najpierw zlokalizuj, a następnie wykonaj step in polecenie debugowania. Dostęp do polecenia

W przypadku użytkowników programu Visual Studio

W programie Visual Studio dostęp do polecenia step in dostęp do polecenia można uzyskać poprzez menu Debugowanie > Krok do lub naciskając klawisz skrótu F11. Dostęp do polecenia

W przypadku użytkowników Code::Blocks

W Code::Blocks, dostęp do polecenia step in dostęp do polecenia można uzyskać poprzez Menu debugowania > Krok do lub naciskając przycisk Kombinacja skrótów Shift-F7.

Dla użytkowników VS Code

W VS Code polecenie step in dostęp do polecenia można uzyskać poprzez Uruchom > Krok do.

W przypadku innych kompilatorów/IDE

Jeśli używasz innego IDE, prawdopodobnie znajdziesz step in polecenie w menu Debuguj lub Uruchom.

Kiedy program nie jest uruchomiony, a ty wykonaj pierwsze polecenie debugowania, możesz zobaczyć kilka rzeczy:

  • W razie potrzeby program przekompiluje się ponownie.
  • Program zacznie działać. Ponieważ nasza aplikacja jest programem konsolowym, powinno otworzyć się okno wyjściowe konsoli. Będzie pusty, ponieważ jeszcze niczego nie wygenerowaliśmy.
  • Twoje IDE może otworzyć niektóre okna diagnostyczne, które mogą mieć nazwy takie jak „Narzędzia diagnostyczne”, „Stos wywołań” i „Obserwuj”. Omówimy niektóre z nich później — na razie możesz je zignorować.

Ponieważ zrobiliśmy step in, powinieneś teraz zobaczyć jakiś znacznik pojawiający się na lewo od nawiasu otwierającego funkcji głównego (wiersz 9). W programie Visual Studio ten znacznik jest żółtą strzałką (Code::Blocks używa żółtego trójkąta). Jeśli używasz innego IDE, powinieneś zobaczyć coś, co służy temu samemu celowi.

Ta strzałka wskazuje, że wskazywana linia zostanie wykonana jako następna. W tym przypadku debugger mówi nam, że następną linią do wykonania jest nawias otwierający funkcji głównego (linia 9).

Wybierz step in (używając odpowiedniego polecenia dla Twojego IDE, wymienionego powyżej), aby wykonać nawias otwierający, a strzałka przesunie się do następnej instrukcji (linia 10).

Oznacza to, że następną linią, która zostanie wykonana, będzie wywołanie funkcji funkcja printValue.

Wybierz step in ponownie. Ponieważ ta instrukcja zawiera wywołanie funkcji printValue, wkraczamy do funkcji, a strzałka przesunie się na górę treści printValue (linia 4).

Wybierz step in ponownie, aby wykonać nawias otwierający funkcja printValue, która przesunie strzałkę do linii 5.

Wybierz step in jeszcze raz, co spowoduje wykonanie instrukcji std::cout << value << '\n' i przesunięcie strzałki do linii 6.

Ostrzeżenie

Wersja operatora<< użytego na wyjściu jest zaimplementowana jako funkcja. W rezultacie Twoje IDE może zamiast tego wkroczyć w implementację funkcji operator<<.

Jeśli tak się stanie, zobaczysz, że Twoje IDE otwiera nowy plik z kodem, a znacznik strzałki przesunie się na górę funkcji o nazwie operator<< (jest to część standardowej biblioteki). Zamknij plik kodu, który właśnie został otwarty, następnie znajdź i wykonaj wyjdź debug (instrukcje znajdują się poniżej w sekcji „wyjdź”, jeśli potrzebujesz pomocy).

Teraz, ponieważ std::cout << value << '\n' wykonało się, powinniśmy zobaczyć wartość 5 pojawiającą się w oknie konsoli.

Wskazówka

Wspominaliśmy o tym we wcześniejszej lekcji że std::cout jest buforowane, co oznacza, że może wystąpić opóźnienie między poproszeniem std::cout o wydrukowanie wartości a momentem, w którym faktycznie to nastąpi. Z tego powodu wartość 5 może nie być widoczna w tym momencie. Aby mieć pewność, że wszystkie dane wyjściowe std::cout zostaną natychmiast wyprowadzone, możesz tymczasowo dodać następującą instrukcję na początek funkcji main():

std::cout << std::unitbuf; // enable automatic flushing for std::cout (for debugging)

Ze względu na wydajność, ta instrukcja powinna zostać usunięta lub zakomentowana po debugowaniu.

Jeśli nie chcesz ciągle dodawać/usuwać/komentować/odkomentowywać powyższego, możesz zawinąć tę instrukcję w dyrektywę preprocesora kompilacji warunkowej (opisane w lekcja 2.10 — Wprowadzenie do preprocesora):

#ifdef DEBUG
std::cout << std::unitbuf; // enable automatic flushing for std::cout (for debugging)
#endif

Musisz się upewnić, że makro preprocesora DEBUG jest zdefiniowane gdzieś powyżej tej instrukcji lub jako część ustawień kompilatora.

Wybierz step in ponownie, aby wykonać nawias zamykający funkcji printValue. W tym momencie printValue zakończył wykonywanie i kontrola zostaje zwrócona do głównego.

Zauważysz, że strzałka ponownie wskazuje na printValue!

Chociaż możesz pomyśleć, że debuger zamierza ponownie wywołać printValue , w rzeczywistości debuger po prostu informuje Cię, że wraca z funkcji call.

Wybierz step in jeszcze trzy razy. W tym momencie wykonaliśmy wszystkie linie w naszym programie, więc gotowe. Niektóre debugery automatycznie zakończą w tym momencie sesję debugowania, inne mogą nie. Jeśli Twój debuger tego nie zrobi, może być konieczne znalezienie w menu polecenia „Zatrzymaj debugowanie” (w Visual Studio znajduje się to w obszarze Debugowanie > Zatrzymaj. Debugowanie).

Zauważ to Zatrzymaj debugowanie można użyć w dowolnym momencie procesu debugowania, aby zakończyć sesję debugowania.

Gratulacje, przeszedłeś przez program i widziałeś wykonanie każdej linii!

Wskazówka

W przyszłych lekcjach omówimy inne polecenia debugera, z których niektóre mogą nie być dostępne, chyba że debuger jest już uruchomiony. Jeśli żądane polecenie debugowania nie jest dostępne, step in swój kod, aby uruchomić debuger i spróbować ponownie.

Step over

Podobnie jak step in, polecenie przekroczenie wykonuje następną instrukcję w normalnej ścieżce wykonywania programu. Jednakże, podczas gdy step in wprowadzi wywołania funkcji i wykona je wiersz po wierszu, przekroczenie wykona całą funkcję bez zatrzymywania i przywróci kontrolę po wykonaniu funkcji.

W przypadku użytkowników programu Visual Studio

W programie Visual Studio dostęp do polecenia przekroczenie dostęp do polecenia można uzyskać poprzez menu debugowania > Step Over lub naciskając klawisz skrótu F10.

W przypadku użytkowników Code::Blocks

W Code::Blocks, dostęp do polecenia przekroczenie polecenie jest zamiast tego wywoływany jest Następny wiersz i można uzyskać do niego dostęp poprzez menu debugowania > Następna linia lub przez naciśnięcie klawisza skrótu F7.

Dla użytkowników VS Code

W VS Code polecenie przekroczenie dostęp do polecenia można uzyskać poprzez Uruchom > Krok Over lub naciskając klawisz skrótu F10 klawisz skrótu.

Przyjrzyjmy się przykładowi, w którym przechodzimy przez wywołanie funkcji do printValue:

#include <iostream>

void printValue(int value)
{
    std::cout << value << '\n';
}

int main()
{
    printValue(5);

    return 0;
}

Najpierw użyj step in w swoim programie, aż znacznik wykonania znajdzie się w linii 10:

Teraz wybierz przekroczenie. Debuger wykona funkcję (która wypisuje wartość 5 w oknie wyjściowym konsoli), a następnie zwróci kontrolę w następnej instrukcji (linia 12). Polecenie

Klasa przekroczenie zapewnia wygodny sposób pomijania funkcji, gdy masz pewność, że już działają lub nie jesteś zainteresowany ich teraz debugowaniem.

Step out

W przeciwieństwie do pozostałych dwóch poleceń krokowych, Step out nie wykonuje tylko następnego wiersza kodu. Zamiast tego wykonuje cały pozostały kod aktualnie wykonywanej funkcji, a następnie zwraca kontrolę po jej zwróceniu.

W przypadku użytkowników programu Visual Studio

W programie Visual Studio dostęp do polecenia wyjdź dostęp do polecenia można uzyskać poprzez menu debugowania > Wyjdź lub naciskając kombinację skrótów Shift-F11.

W przypadku użytkowników Code::Blocks

W Code::Blocks, dostęp do polecenia wyjdź dostęp do polecenia można uzyskać poprzez menu debugowania > Wyjdź lub naciskając kombinację skrótów Ctrl-F7.

Dla użytkowników VS Code

W VS Code polecenie wyjdź dostęp do polecenia można uzyskać poprzez Uruchom > Krok Out lub naciskając klawisz skrótu shift+F11 kombinacja skrótów.

Przyjrzyjmy się temu przykładowi, używając tego samego programu co powyżej:

#include <iostream>

void printValue(int value)
{
    std::cout << value << '\n';
}

int main()
{
    printValue(5);

    return 0;
}

Krok w program, dopóki nie znajdziesz się w funkcji printValue, ze znacznikiem wykonania na linii 4.

Następnie wybierz wyjdź. Zauważysz, że w oknie wyjściowym pojawi się wartość 5 , a debuger zwróci Ci kontrolę po zakończeniu funkcji (w linii 10).

To polecenie jest najbardziej przydatne, gdy przypadkowo wejdziesz do funkcji, której nie chcesz debugować.

O krok za daleko

Przechodząc przez program, zwykle możesz przejść tylko do przodu. Bardzo łatwo jest przypadkowo ominąć (przekroczyć) miejsce, które chciałeś zbadać.

Jeśli przekroczysz zamierzony cel, zwykle należy przerwać debugowanie i ponownie uruchomić debugowanie, uważając, aby tym razem nie minąć celu.

Odsuń się

Niektóre debugery (takie jak Visual Studio Enterprise Edition i rr) wprowadziły funkcję krokową ogólnie określaną jako cofnij się lub debugowanie wsteczne. Celem cofnij się jest przewinięcie ostatniego kroku, aby można było przywrócić program do poprzedniego stanu. Może to być przydatne, jeśli przekroczysz granicę lub jeśli chcesz ponownie sprawdzić właśnie wykonaną instrukcję.

Wdrażanie cofnij się wymaga dużego wyrafinowania ze strony debugera (ponieważ musi on śledzić oddzielny stan programu dla każdego kroku). Ze względu na złożoność ta funkcja nie jest jeszcze ustandaryzowana i różni się w zależności od debugera. W momencie pisania tego tekstu (styczeń 2019 r.) ani edycja Visual Studio Community, ani najnowsza wersja Code::Blocks nie obsługują tej możliwości. Miejmy nadzieję, że w pewnym momencie w przyszłości trafi do tych produktów i będzie dostępny do szerszego zastosowania.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
416 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze