Pochopenie alokácie pamäte v Delphi
Getty Images/Daniel Sambraus
Zavolajte funkciu 'DoStackOverflow' raz z váš kód a dostanete EStackOverflow chyba, ktorú vyvolalo Delphi so správou 'pretečenie zásobníka'.
|_+_||_+_||_+_|
|_+_|
|_+_|
Čo je tento „zásobník“ a prečo tam dochádza k pretečeniu pomocou vyššie uvedeného kódu?
Funkcia DoStackOverflow sa teda rekurzívne volá sama seba – bez „stratégie odchodu“ – stále sa točí a nikdy neustúpi.
Rýchla oprava, ktorú by ste urobili, je odstrániť zjavnú chybu, ktorú máte, a zabezpečiť, aby funkcia v určitom bode existovala (takže váš kód môže pokračovať vo vykonávaní od miesta, kde ste funkciu zavolali).
Idete ďalej a nikdy sa nepozeráte späť, nestaráte sa o chybu/výnimku, ako je teraz vyriešená.
Otázkou však zostáva: čo je tento zásobník a prečo dochádza k pretečeniu ?
Pamäť vo vašich aplikáciách Delphi
Keď začnete programovať v Delphi, môžete sa stretnúť s chybou ako je tá vyššie, vyriešite ju a pôjdete ďalej. Toto súvisí s alokáciou pamäte. Väčšinu času by ste sa nestarali o prideľovanie pamäte tak dlho ako vy zadarmo, čo vytvoríte .
Keď získate viac skúseností v Delphi, začnete vytvárať svoje vlastné triedy, vytvárať ich inštancie, starať sa o správu pamäte a podobne.
Dostanete sa do bodu, kedy si v Pomocníkovi prečítate niečo ako „Lokálne premenné (deklarované v rámci procedúr a funkcií) sa nachádzajú v aplikácii stoh .' a tiež Triedy sú referenčné typy, takže sa nekopírujú pri priradení, odovzdávajú sa odkazom a prideľujú sa na hromada .
Takže, čo je „hromada“ a čo je „hromada“?
Hromada vs. Hromada
Spustenie aplikácie v systéme Windows , v pamäti sú tri oblasti, kde vaša aplikácia ukladá údaje: globálna pamäť, halda a zásobník.
Globálne premenné (ich hodnoty/údaje) sú uložené v globálnej pamäti. Pamäť pre globálne premenné je rezervovaná vašou aplikáciou pri spustení programu a zostáva alokovaná, kým sa váš program neskončí. Pamäť pre globálne premenné sa nazýva „údajový segment“.
Keďže globálna pamäť je len raz pridelená a uvoľnená pri ukončení programu, v tomto článku sa tým nestaráme.
Zásobník a halda sú miesta, kde dochádza k dynamickej alokácii pamäte: keď vytvoríte premennú pre funkciu, keď vytvoríte inštanciu triedy, keď odošlete parametre do funkcie a použijete/odovzdáte jej výslednú hodnotu.
Čo je zásobník?
Keď deklarujete premennú vo funkcii, pamäť potrebná na uloženie premennej sa pridelí zo zásobníka. Jednoducho napíšete 'var x: integer', vo svojej funkcii použijete 'x' a keď funkcia skončí, nestaráte sa o pridelenie pamäte ani o uvoľnenie. Keď premenná prekročí rozsah (kód ukončí funkciu), uvoľní sa pamäť, ktorá bola prevzatá v zásobníku.
Pamäť zásobníka sa prideľuje dynamicky pomocou prístupu LIFO ('posledný dnu prvý von').
In programy Delphi , zásobníková pamäť používa
- Lokálne rutinné (metóda, procedúra, funkcia) premenné.
- Parametre rutiny a typy návratov.
- Funkcia Windows APIhovory.
- Záznamy (to je dôvod, prečo nemusíte explicitne vytvárať inštanciu typu záznamu).
Pamäť v zásobníku nemusíte explicitne uvoľňovať, pretože pamäť sa vám automaticky magicky pridelí, keď napríklad deklarujete lokálnu premennú do funkcie. Keď sa funkcia ukončí (niekedy aj predtým kvôli optimalizácii kompilátora Delphi), pamäť pre premennú sa automaticky magicky uvoľní.
Veľkosť zásobníka pamäteje štandardne dostatočne veľká pre vaše (také zložité, ako sú) programy Delphi. Hodnoty 'Maximum Stack Size' a 'Minimum Stack Size' v možnostiach Linker pre váš projekt určujú predvolené hodnoty – na 99,99 % by ste to nemuseli meniť.
Predstavte si zásobník ako hromadu pamäťových blokov. Keď deklarujete/používate lokálnu premennú, správca pamäte Delphi vyberie blok zhora, použije ho a keď už nie je potrebný, vráti sa späť do zásobníka.
Pri použití lokálnej premennej pamäte zo zásobníka sa lokálne premenné pri deklarácii neinicializujú. Deklarujte premennú 'var x: integer' v nejakej funkcii a skúste si prečítať hodnotu pri zadávaní funkcie -- x bude mať nejakú 'čudnú' nenulovú hodnotu. Takže vždy inicializujte (alebo nastavte hodnotu) na svoje lokálne premenné predtým, ako si prečítate ich hodnotu.
Vďaka LIFO sú operácie zásobníka (alokácia pamäte) rýchle, pretože na správu zásobníka je potrebných len niekoľko operácií (push, pop).
Čo je halda?
Halda je oblasť pamäte, v ktorej je uložená dynamicky alokovaná pamäť. Keď vytvoríte inštanciu triedy, pamäť sa pridelí z haldy.
V programoch Delphi sa haldová pamäť používa tým/kedy
- Vytvorenie inštancie triedy.
- Vytváranie a zmena veľkosti dynamických polí.
- Explicitné prideľovanie pamäte pomocou GetMem, FreeMem, New a Dispose().
- Používanie ANSI/wide/Unicode reťazcov, variantov, rozhraní (automaticky spravovaných Delphi).
Pamäť haldy nemá pekné rozloženie, kde by bol nejaký poriadok pri prideľovaní blokov pamäte. Halda vyzerá ako plechovka guľôčok. Pridelenie pamäte z haldy je náhodné, blok odtiaľto ako blok odtiaľ. Operácie haldy sú teda o niečo pomalšie ako operácie v zásobníku.
Keď požiadate o nový pamäťový blok (t. j. vytvoríte inštanciu triedy), správca pamäte Delphi to vyrieši za vás: dostanete nový pamäťový blok alebo použitý a vyradený.
Halda pozostáva zo všetkej virtuálnej pamäte ( RAM a miesto na disku ).
Manuálne prideľovanie pamäte
Teraz, keď je všetko o pamäti jasné, môžete bezpečne (vo väčšine prípadov) ignorovať vyššie uvedené a jednoducho pokračovať v písaní programov Delphi tak, ako ste to robili včera.
Samozrejme, mali by ste vedieť, kedy a ako manuálne prideliť/uvoľniť pamäť.
'EStackOverflow' (zo začiatku článku) bolo vznesené, pretože pri každom volaní DoStackOverflow sa zo zásobníka použil nový segment pamäte a zásobník má obmedzenia. Také jednoduché.