Heap vs. Zásobník pro vývojáře Delphi

Volejte funkci „DoStackOverflow“ jednou od váš kód a dostanete EStackOverflow chyba vyvolaná Delphi se zprávou „přetečení zásobníku“.


funkce DoStackOverflow: integer;

začít

 výsledek: = 1 + DoStackOverflow;

konec;

Co je to „hromada“ a proč je zde přetečení pomocí výše uvedeného kódu?

Funkce DoStackOverflow se tedy rekurzivně nazývá sama - bez „strategie ukončení“ - pouze se otáčí a nikdy nekončí.

Rychlá oprava, kterou byste udělali, je vyčistit zjevnou chybu, kterou máte, a zajistit, aby funkce v určitém okamžiku existovala (aby váš kód mohl pokračovat v provádění od místa, kde jste funkci nazvali).

Jdete dál a nikdy se neohlížíte, nestaráte se o chybu / výjimku, protože je nyní vyřešena.

Otázkou však zůstává: co je tento stack a proč je přetečení?

Paměť ve vašich aplikacích Delphi

Když začnete programovat v Delphi, můžete zaznamenat chybu jako ta výše, vyřešili byste ji a pokračovali. Tento souvisí s přidělením paměti. Většinu času byste se o přidělení paměti nestarali tak dlouho, jako vy zdarma, co vytvoříte.

instagram viewer

Jak získáváte více zkušeností v Delphi, začnete vytvářet své vlastní třídy, vytvářet jejich instance, starat se o správu paměti a podobně.

Dostanete se k bodu, kde si v Nápovědě přečtete něco podobného „Lokální proměnné (deklarované v rámci procedur a funkcí) jsou umístěny v aplikacích zásobník." a také Třídy jsou referenční typy, takže se při přiřazování nekopírují, jsou předávány odkazem a jsou přidělovány na halda.

Co je tedy "stack" a co je "halda"?

Stack vs. Halda

Spuštění aplikace v systému Windows, v paměti jsou tři oblasti, ve kterých aplikace ukládá data: globální paměť, halda a zásobník.

Globální proměnné (jejich hodnoty / data) jsou uloženy v globální paměti. Paměť pro globální proměnné je při spuštění programu rezervována aplikací a zůstává přidělena, dokud se program neskončí. Paměť pro globální proměnné se nazývá „datový segment“.

Protože globální paměť je při ukončení programu přidělena a uvolněna pouze jednou, nezajímá nás to v tomto článku.

Zásobník a halda jsou místem, kde dochází k dynamickému přidělování paměti: když vytvoříte pro funkci proměnnou, když vytvoříte instanci třídy, když odešlete parametry funkci a použijete / předáte její výsledek hodnota.

Co je zásobník?

Když deklarujete proměnnou uvnitř funkce, je paměť potřebná k držení proměnné přidělena ze zásobníku. Jednoduše píšete „var x: integer“, použijete ve své funkci „x“ a když funkce skončí, nestaráte se o přidělení paměti ani uvolnění. Když proměnná zmizí z rozsahu (kód opustí funkci), je paměť, která byla přijata do zásobníku, uvolněna.

Paměť zásobníku je přidělena dynamicky pomocí přístupu LIFO ("last in first out").

v Delphi programy, paměť zásobníku používá

  • Proměnné lokální rutiny (metoda, procedura, funkce).
  • Rutinní parametry a návratové typy.
  • Funkce rozhraní Windows API hovory.
  • Záznamy (proto nemusíte explicitně vytvářet instanci typu záznamu).

Nemusíte explicitně uvolňovat paměť v zásobníku, protože paměť je pro vás automaticky magicky přiřazena, když například deklarujete místní proměnnou funkci. Když funkce skončí (někdy i dříve kvůli optimalizaci kompilátoru Delphi), paměť pro proměnnou bude automaticky magicky uvolněna.

Velikost paměti zásobníku je ve výchozím nastavení dostatečně velká pro vaše (tak složité, jaké jsou) programy Delphi. Hodnoty „Maximální velikost zásobníku“ a „Minimální velikost zásobníku“ v možnostech Linker pro váš projekt určují výchozí hodnoty - v 99,99% byste to nemuseli měnit.

Představte si hromadu jako hromadu paměťových bloků. Když deklarujete / použijete lokální proměnnou, správce paměti Delphi vybere blok shora, použije jej a pokud již není potřeba, vrátí se zpět do zásobníku.

Po použití paměti lokálních proměnných ze zásobníku nejsou lokální proměnné při deklarování inicializovány. Deklarujte proměnnou "var x: integer" v nějaké funkci a zkuste načíst hodnotu, když zadáte funkci - x bude mít nějakou "divnou" nenulovou hodnotu. Vždy tedy před načtením jejich hodnoty inicializujte (nebo nastavte hodnotu) do místních proměnných.

Kvůli LIFO jsou operace zásobníku (alokace paměti) rychlé, protože ke správě zásobníku je zapotřebí jen několik operací (push, pop).

Co je halda?

Halda je oblast paměti, ve které je uložena dynamicky přidělená paměť. Při vytváření instance třídy je paměť přidělena z haldy.

V programech Delphi je paměť haldy používána / kdy

  • Vytvoření instance třídy.
  • Vytváření a změna velikosti dynamických polí.
  • Explicitní alokace paměti pomocí GetMem, FreeMem, New and Dispose ().
  • Používání řetězců, variant, rozhraní ANSI / wide / Unicode (řízeno automaticky společností Delphi).

Hromadná paměť nemá žádné pěkné rozvržení, kde by bylo nějaké pořadí, je přidělování bloků paměti. Halda vypadá jako plechovka kuliček. Přidělení paměti z haldy je náhodné, blok odtud než blok odtud. Operace haldy jsou tedy o něco pomalejší než operace na zásobníku.

Když požádáte o nový blok paměti (tj. Vytvoříte instanci třídy), správce paměti Delphi to za vás vyřeší: dostanete nový blok paměti nebo použitý a vyřazený blok.

Hromadu tvoří veškerá virtuální paměť (RAM a místo na disku).

Ručně přidělené paměti

Nyní, když je vše o paměti jasné, můžete bezpečně (ve většině případů) ignorovat výše uvedené a jednoduše pokračovat v psaní programů Delphi, jako jste to dělali včera.

Samozřejmě byste si měli být vědomi, kdy a jak ručně přidělit / uvolnit paměť.

„EStackOverflow“ (od začátku článku) byl zvýšen, protože při každém volání DoStackOverflow byl ze zásobníku použit nový segment paměti a zásobník má omezení. Tak jednoduché.