Zrozumienie alokacji pamięci w Delphi
Getty Images/Daniel Sambraus
Wywołaj funkcję „DoStackOverflow” raz z Twój kod a dostaniesz Przepełnienie stosu błąd zgłoszony przez Delphi z komunikatem „przepełnienie stosu”.
|_+_||_+_||_+_|
|_+_|
|_+_|
Co to za „stos” i dlaczego jest tam przepełnienie przy użyciu powyższego kodu?
Tak więc funkcja DoStackOverflow rekursywnie wywołuje samą siebie – bez „strategii wyjścia” – po prostu kręci się i nigdy nie wychodzi.
Szybkim rozwiązaniem, które możesz zrobić, jest usunięcie oczywistego błędu, który masz, i upewnienie się, że funkcja istnieje w pewnym momencie (aby Twój kod mógł kontynuować wykonywanie od miejsca, w którym wywołałeś funkcję).
Idziesz dalej i nigdy nie oglądasz się za siebie, nie przejmując się błędem/wyjątkiem, który został już rozwiązany.
Pozostaje jednak pytanie: co to za stos i dlaczego jest przepełnienie ?
Pamięć w aplikacjach Delphi
Kiedy zaczynasz programować w Delphi, możesz doświadczyć błędu takiego jak ten powyżej, rozwiążesz go i przejdziesz dalej. Ten jest związany z alokacją pamięci. Przez większość czasu nie dbasz o alokację pamięci, dopóki ty uwolnij to, co tworzysz .
W miarę zdobywania doświadczenia w Delphi zaczynasz tworzyć własne klasy, tworzyć ich instancje, dbać o zarządzanie pamięcią i tym podobne.
Dojdziesz do momentu, w którym przeczytasz w Pomocy coś takiego: „Zmienne lokalne (deklarowane w ramach procedur i funkcji) znajdują się w aplikacji” stos . i również Klasy są typami referencyjnymi, więc nie są kopiowane przy przypisaniu, są przekazywane przez referencję i alokowane na sterta .
Czym więc jest „stos”, a co „sterta”?
Stos kontra sterta
Uruchamianie aplikacji w systemie Windows , w pamięci znajdują się trzy obszary, w których aplikacja przechowuje dane: pamięć globalna, sterta i stos.
Zmienne globalne (ich wartości/dane) są przechowywane w pamięci globalnej. Pamięć dla zmiennych globalnych jest zarezerwowana przez twoją aplikację podczas uruchamiania programu i pozostaje przydzielona aż do zakończenia programu. Pamięć dla zmiennych globalnych nazywana jest „segmentem danych”.
Ponieważ pamięć globalna jest przydzielana i zwalniana tylko raz przy zakończeniu programu, nie przejmujemy się tym w tym artykule.
Stos i sterta to miejsce, w którym odbywa się dynamiczna alokacja pamięci: kiedy tworzysz zmienną dla funkcji, kiedy tworzysz instancję klasy, kiedy wysyłasz parametry do funkcji i używasz/przekazujesz jej wartość wynikową.
Co to jest stos?
Kiedy deklarujesz zmienną wewnątrz funkcji, pamięć wymagana do przechowywania zmiennej jest przydzielana ze stosu. Po prostu piszesz 'var x: integer', używasz 'x' w swojej funkcji, a kiedy funkcja kończy działanie, nie przejmujesz się alokacją pamięci ani zwalnianiem. Gdy zmienna wychodzi poza zakres (kod wychodzi z funkcji), pamięć, która została zabrana na stos, zostaje zwolniona.
Pamięć stosu jest przydzielana dynamicznie przy użyciu podejścia LIFO („ostatni wchodzi, pierwszy wychodzi”).
W Programy Delphi , pamięć stosu jest używana przez
- Zmienne procedury lokalnej (metody, procedury, funkcji).
- Parametry rutynowe i typy zwracane.
- Funkcja API Windowswzywa.
- Rekordy (dlatego nie musisz jawnie tworzyć instancji typu rekordu).
Nie musisz jawnie zwalniać pamięci na stosie, ponieważ pamięć jest automatycznie przydzielana, gdy na przykład deklarujesz zmienną lokalną do funkcji. Kiedy funkcja kończy działanie (czasami nawet wcześniej z powodu optymalizacji kompilatora Delphi) pamięć dla zmiennej zostanie automatycznie zwolniona.
Rozmiar pamięci stosujest domyślnie wystarczająco duży dla twoich (tak złożonych jak są) programów Delphi. Wartości „Maksymalny rozmiar stosu” i „Minimalny rozmiar stosu” w opcjach konsolidatora dla twojego projektu określają wartości domyślne – w 99,99% nie musisz tego zmieniać.
Pomyśl o stosie jako stosie bloków pamięci. Kiedy zadeklarujesz/użyjesz zmienną lokalną, menedżer pamięci Delphi wybierze blok od góry, użyje go, a gdy nie będzie już potrzebny, zostanie zwrócony z powrotem na stos.
Mając używaną pamięć zmiennych lokalnych ze stosu, zmienne lokalne nie są inicjowane podczas deklarowania. Zadeklaruj zmienną 'var x: integer' w jakiejś funkcji i po prostu spróbuj odczytać wartość podczas wprowadzania funkcji -- x będzie miało jakąś 'dziwną' niezerową wartość. Dlatego zawsze inicjuj (lub ustawiaj wartość) zmiennych lokalnych przed odczytaniem ich wartości.
Dzięki LIFO operacje na stosie (alokacja pamięci) są szybkie, ponieważ do zarządzania stosem potrzeba tylko kilku operacji (push, pop).
Co to jest sterta?
Sterta to region pamięci, w którym przechowywana jest pamięć przydzielona dynamicznie. Kiedy tworzysz instancję klasy, pamięć jest przydzielana ze sterty.
W programach Delphi pamięć sterty jest używana przez/kiedy
- Tworzenie instancji klasy.
- Tworzenie i zmiana rozmiaru tablic dynamicznych.
- Jawne przydzielanie pamięci za pomocą GetMem, FreeMem, New i Dispose().
- Używanie ciągów ANSI/wide/Unicode, wariantów, interfejsów (zarządzanych automatycznie przez Delphi).
Pamięć sterty nie ma ładnego układu, w którym byłaby pewna kolejność przydzielania bloków pamięci. Sterta wygląda jak puszka kulek. Przydział pamięci ze sterty jest losowy, blok stąd niż blok stamtąd. W ten sposób operacje na stercie są nieco wolniejsze niż te na stosie.
Kiedy poprosisz o nowy blok pamięci (tj. utworzysz instancję klasy), menedżer pamięci Delphi zajmie się tym za Ciebie: otrzymasz nowy blok pamięci lub używany i odrzucony.
Sterta składa się z całej pamięci wirtualnej ( RAM i miejsce na dysku ).
Ręczne przydzielanie pamięci
Teraz, gdy wszystko o pamięci jest jasne, możesz bezpiecznie (w większości przypadków) zignorować powyższe i po prostu kontynuować pisanie programów Delphi tak, jak robiłeś to wczoraj.
Oczywiście powinieneś wiedzieć, kiedy i jak ręcznie przydzielać/zwalniać pamięć.
Zgłoszono „EStackOverflow” (od początku artykułu), ponieważ przy każdym wywołaniu DoStackOverflow używany był nowy segment pamięci ze stosu, a stos ma ograniczenia. Tak proste jak to.