Witam i zapraszam.
W tym wykładzie chcę opowiedzieć trochę o stosie wywołań, czym dokładnie jest i jak możesz go odczytać,
aby dodatkowo pomóc w debugowaniu twojego programu.
Ślad stosu, który jest zasadniczo tym, czym jest stos wywołań, jest generowany za każdym razem, gdy twoja aplikacja ulega
awarii z powodu błędu krytycznego.
Powodem, dla którego używany jest termin stos, jest to, że odnosi się do struktury danych, która jest powszechna w programowaniu.
Stack to struktura danych, która jest znana jako LIFO, last in, first out, i tak właśnie przechowywane
są dane.
Ostatni dodany element danych jest pierwszym, który pobierasz z powrotem.
Możesz pomyśleć o tym, że jesz stos naleśników, pierwszy naleśnik jest na dole, który
dodajesz.
Ostatni naleśnik jest na górze.
Kiedy idziesz je zjeść, zjadasz ostatnią, która została dodana.
I tak ślad stosu jest bardzo przydatny, gdy twój program ulega awarii, ponieważ zawiera informacje, które pomagają
w debugowaniu.
Tak więc ślad stosu pokazuje listę wywołań funkcji, które prowadzą do błędu.
A co się stanie, to w programie, gdy wywołasz swoją główną funkcję, to jest punkt wejścia.
Funkcja główna będzie wywoływać inne funkcje.
Te funkcje będą wywoływać inne funkcje, a wszystkie te funkcje, które dostaną się do realizacji lub zostaną wywołane,
zostaną umieszczone na stosie.
I tak możesz zrobić to, kiedy program się zawiesza, stos wywołań powie ci, że ostatnia funkcja, która
została wywołana i konkretna linia kodu, która spowodowała błąd.
Tak więc Callstack zawiera wiele dobrych informacji.
Będzie on zawierał nazwę pliku i numery linii kodu, który powoduje wystąpienie wyjątku lub
błędu fatalnego.
Wierzchołek stosu zawiera ostatnie wywołanie, które spowodowało błąd i mogą istnieć zagnieżdżone połączenia.
Na dole stosu znajduje się pierwsze wywołanie, które rozpoczęło łańcuch wywołań w celu wywołania błędu.
Więc jeśli zwykle w C, który będzie w głównej procedurze, wywołujesz główną, główna wywołuje inną funkcję, która wywołuje
inną funkcję i tak dalej i tak dalej.
I wtedy ta ostatnia linijka jest szczegółowa.
Jest to bardzo, bardzo pomocne, gdy aplikacja na aplikację się zawiesza.
I tak, gdy program C ulega awarii, może wygenerować błąd segmentacji, a to nie mówi ci wiele informacji.
To po prostu oznacza, że stało się coś złego.
To również wygeneruje plik rdzenia.
Możesz przeanalizować plik rdzenia, który zawiera stos wywołań, który powie ci dokładną linię kodu,
która spowodowała problem dla twojej awarii.
Musisz więc znaleźć wywołanie w swojej aplikacji, które powoduje awarię.
A stos wywołań jest prawdopodobnie najlepszym sposobem, aby to zrobić.
I nie zawsze trzeba zaglądać do tego pliku z rdzeniem.
Możesz również przeanalizować stos wywołań w użyciu GDB z debuggerem.
I chcę wam pokazać, jak to wygląda.
Programista jest również w stanie zrzucić ślad stosu w dowolnym momencie.
Więc jeśli złapiesz błąd lub rozpoznasz, że może wystąpić błąd śmiertelny, możesz zrzucić stos wywołań,
aby zobaczyć, co się dzieje.
Jeśli faktycznie nie wiesz, kiedy błąd się pojawi, nadal możesz zrzucić stos wywołań,
aby zobaczyć bieżące ślady, które doprowadziły do konkretnego połączenia.
Podczas korzystania z GDB, stos wywołań będzie wyglądał coś takiego.
Możesz uzyskać dostęp do stosu wywołań poprzez menu, gdy uruchamiasz debugger
w GDB i pokażę ci, jak to zrobić, ale tak to wygląda i to, co zobaczysz tutaj, ma numer,
adres, a następnie funkcję.
Ten konkretny stos wywołań tutaj ma wiele różnych połączeń, więc musiał wykonać wiele różnych zagnieżdżeń
i wiele różnych wywołań funkcji.
Ale na samym dole stosu w linii 22 mówi GTK main.
Oznacza to, że.
Oznacza to, że prawdopodobnie wykonał on główną funkcję.
A następnie w liniach 21, wygląda na to, że wykonał.
Może oświadczenie mówi, że główna pętla jest uruchamiana.
A potem wygląda to jak wywołana funkcja o nazwie G main context iterate.
I wewnątrz tej funkcji, to tam było jakieś ilekroć było inne wywołanie do funkcji wysyłki głównego
kontekstu IAG.
Więc była funkcja iteracji, potem funkcja wysyłki, a następnie znaki zapytania w linii 18 oznaczają,
że nie ma informacji o debugowaniu.
Nie wie, co się tam stało.
I dlatego, gdy budujesz swój program, możesz go zbudować do wydania lub budujesz go z informacjami
o debugowaniu.
Kiedy budujesz go z informacjami o debugowaniu, zawiera on dodatkowe informacje podczas kompilacji, aby po uruchomieniu
można było spojrzeć na stosy wywołań.
Kiedy go wypuszczasz, zbuduj coś do wydania.
Plik wykonywalny jest o wiele mniejszy i może nie zawierać tych informacji.
Może zawierać więcej znaków zapytania.
Ale to powie ci również, jeśli wywołałeś jakieś biblioteki, jakieś dodatkowe programy, to wywoła te i
powie ci stos wywołań stamtąd.
Ale to, co pokazuje, ponownie, to różne zagnieżdżone zdarzenia dla połączenia na samej górze jest to,
co jest ważne, ponieważ będzie oznaczać, co spowodowało awarię.
Wygląda więc na to, że w tym konkretnym stosie wywołań, wewnętrzne odświeżenie powodowało problem.
I możesz zobaczyć, że od linii 22 do linii zerowej, że istnieje wiele różnych inwokacji różnych
kodów.
Ale ewentualna awaria programu w linii zerowej wywołuje stosy.
Możesz także spojrzeć na stosy wywołań podczas działania programu, aby po prostu zobaczyć informacje, abyś mógł je prześledzić.
Więc jeśli umieścisz punkt przerwania wewnątrz funkcji, debugujesz, uruchamiasz debugger na tym.
Debugger zatrzyma się na tym punkcie przerwania.
Następnie możesz przywołać stos wywołań i powie ci, jak doszedłeś do tej linii kodu.
Ta funkcja została wywołana przed tą funkcją.
I znowu, zwykle będzie się zaczynać od głównej funkcji.
Możesz również przejść do różnych ramek, jeśli jesteś zaznajomiony ze stosem i koncepcją i jak
to jest w pamięci, możesz spojrzeć na różne ramki, ale jest to bardzo przydatne narzędzie do znalezienia głównie
awarii w C, i jest bardzo łatwe do odczytania.
Jeśli tylko spojrzysz na górę i nie rozumiesz wiele o stosie wywołań, możesz
spojrzeć na górę i powiedzieć, Och, to jest źródło, to jest linia kodu, która spowodowała awarię.
Oto ładny przykład, który znalazłem w sieci.
Excma.
Edu.
To ilustruje przykłady stosu wywołań funkcji.
Więc jeśli spojrzymy na przykład w kodzie jak zobaczymy mamy po prostu prosty kod.
Wyliczenia nie mają większego znaczenia.
W zasadzie to tylko wywołujemy jakieś funkcje.
Mamy więc funkcję mogrify i funkcję truly half, a main wywoła funkcję mogrify, a następnie wywoła
truly half, a następnie wywoła mogrify, więc możemy zilustrować stos wywołań
i to, jak wygląda na tych połączeniach, w zasadzie tylko ustawiając prosty punkt przerwania w main,
a następnie patrząc na stos wywołań w miarę postępu i wykonywania połączeń.
Więc znowu będziemy bardziej zaznajamiać się z breakpointami i tego typu informacjami.
Ale chcę tylko zademonstrować stos wywołań naprawdę szybko i faktycznie podamy bardziej szczegółowy przykład
stosu wywołań później.
Więc tutaj ustawiam punkt przerwania.
Mogę iść dalej, zbudować i skompilować to i mogę uruchomić debugger i uderzymy w ten punkt przerwania.
Teraz możesz zobaczyć wszystkie menu debuggera po prawej stronie na dole, i zobaczysz
stos wywołań.
Zróbmy to trochę większe.
Ale zaraz po zatrzymaniu się w linii 14, tutaj ma tylko kilka deklaracji przypisania i zobaczysz,
że stos wywołań pokazuje main.
Jeśli przejdziemy nad tym i przejdziemy do następnej linii, możemy faktycznie zobaczyć, że po prostu przechodzi do linii
15 przed wywołaniem Mogrify, prawda?
I tak widzisz, że stos wywołań zostaje z Main.
Jeśli wejdziemy, zobaczysz teraz, że stos wywołań pokazuje Mogrify.
Więc jesteśmy właściwie wewnątrz tej funkcji.
Możemy przejść nad tym do porządku dziennego.
Więc wykonuje pierwszą linię, a następnie zwróci temp.
Nadal pozostaje w tej funkcji, a następnie wraca tam, gdzie robi print F w linii 16, widzisz, jak zmienia
się stos wywołań.
Następnie przechodzimy do linii 18, wywołujemy stos nadal w main i wchodzimy do naprawdę połowicznej funkcji.
I teraz widzisz zmianę stosu wywołań, gdzie pokazuje naprawdę połowę funkcji na górze, zawsze jest to funkcja,
która została ostatnio wywołana.
Ponownie przechodzimy na drugą stronę i widać, że nadal jesteśmy w naprawdę połowie.
Wracamy do Main, wypisujemy coś w linii 19, przechodzimy do linii 21.
Ponownie przeskakujemy do funkcji modify, którą ponownie widzisz na stosie wywołań, Przeskakujemy, przeskakujemy
i ostatecznie wypisujemy końcowy wynik w linii 23 i wracamy z programu.
Ale stos wywołań ponownie pokazuje nam kolejność wywoływanych funkcji od jednej do drugiej.
Więc jeśli masz zagnieżdżone funkcje, może to być coraz bardziej skomplikowane.
Zaletą stosu wywołań jest to, że jeśli masz linię kodu, która ulega awarii, powiedzmy w linii czwartej, będziesz miał
stos wywołań, który zawiera na samej górze, gdzie linia uległa awarii.
I tak możesz faktycznie zobaczyć, przechodząc przez stos wywołań, jaka linia kodu była ostatnią,
która została wywołana, a to powie ci, która linia kodu została rozbita.
Ale będziesz chciał użyć tego narzędzia, gdy twój program się zawiesi.
Dziękuję.