Zásobník volání

Technology
12 hours ago
8
4
2
Avatar
Author
Albert Flores

Zásobník volání je v informatice speciální oblast paměti nebo struktura dat, která slouží k ukládání informací o nevyřízených událostech nebo o předem zazadaných požadavcích na výpočetní prostředky. Jedná se o důležitý prvek v operačních systémech, kde slouží k organizaci a řízení běžících procesů. Volání jsou do zásobníku přidávána a odebírána podle principu LIFO (poslední příchozí, první odcházející). Zásobníky volání se používají pro správu volání funkcí nebo procedur v programovacích jazycích, jako je například C. Přidávání volání do zásobníku a jejich odebírání umožňuje správné vykonávání funkcí v pořadí, ve kterém byly volány. Kromě toho umožňuje přístup k lokálním proměnným funkcí a udržování kontextu mezi jednotlivými voláními, což je důležité při rekurzivních voláních. Zásobník volání má obvykle pevnou velikost, která závisí na operačním systému a architektuře počítače. Při překročení kapacity zásobníku může dojít k chybě vyčerpání paměti a selhání programu. Pro lépe organizovanou správu paměti mohou být použity další techniky, jako je dynamické rozšiřování zásobníku volání. Výhody použití zásobníku volání spočívají v jednoduchosti a efektivitě implementace. Operace přidání nebo odběru volání jsou velmi rychlé a nevyžadují složité algoritmy. Nevýhodou může být omezená kapacita zásobníku a zvýšená náročnost při rekurzivním volání. Proto je důležité vhodně dimensionovat zásobník volání v závislosti na konkrétních potřebách aplikace.

Zásobník volání (často zkrátka zásobník) je v informatice datová struktura typu zásobník, na kterou se při běhu procesu ukládají informace týkající se provádění podprogramů. Přestože správa zásobníku je důležitou součástí prakticky veškerého software, většina programátorů s ním explicitně nepracuje, neboť ve vyšších programovacích jazycích se o správnou funkci zásobníku stará automaticky překladač. Naopak v nízkoúrovňových jazycích, například v jazyce symbolických adres, musí programátor pracovat se zásobníkem explicitně.

Zásobník volání je používán pro několik účelů, přičemž tím hlavním je uložení informace o tom, do jakého stavu se má proces vrátit po ukončení provádění aktuálně běžícího podprogramu. Nejdůležitější takovou informací je návratová adresa, tedy adresa, z níž se má načíst první instrukce po návratu z podprogramu. +more Jako návratová adresa bývá na zásobník volání při provádění instrukce volání podprogramu uložena adresa instrukce následující po volající instrukci.

Popis

Zásobník volání funguje jako každý jiný zásobník, jsou zde dvě základní operace push pro uložení hodnoty na vrchol zásobníku a pop pro vyzvednutí hodnoty z vrcholu zásobníku. Operace uložení je používána především instrukcí volání podprogramu a operace vyzvednutí je používána především instrukcí návratu z podprogramu. +more K oběma operacím může docházet opakovaně a tím je umožněno volání dalšího podprogramu z běžícího podprogramu (a jako speciální případ takových vnořených volání také rekurze).

Pokud je ovšem pro zásobník volání rezervováno příliš málo místa a to je opakovaným ukládáním adres zaplněno (například při nekonečné rekurzi), dochází k přetečení zásobníku, které obvykle způsobí pád programu.

Každý běžící program má alespoň jeden svůj zásobník volání, obvykle jeden pro každé vlákno. Přestože program (respektive vlákno) může pracovat i s jinými datovými strukturami typu zásobník, například při správě signálů nebo při kooperativním multitaskingu, zůstává zásobník volání nejdůležitějším zásobníkem - proto je běžné označovat jej krátce zásobník.

Jednotlivé funkce zásobníku

; Ukládání návratové adresy: je hlavním smyslem zásobníku. V okamžiku zavolání podprogramu je potřeba nějak uchovat informaci, odkud byl podprogram zavolán, respektive kam se má provádění vrátit po ukončení podprogramu. +more Pro tento účel je využit zásobník volání, což má několik výhod: Jednou je, že protože má každý proces respektive vlákno svůj zásobník, nebrání uložení informací tomu, aby byl podprogram reentrantní, tedy aby byl zavolán současně z několika míst. Zároveň je bez dalších problémů možná rekurze.

V závislosti na operačním systému, programovacím jazyku a architektuře počítače je zásobník využíván například pro: Uložení lokálních proměnných: Podprogramy běžně využívají lokální proměnné, tedy adresový prostor používaný pouze v rámci podprogramu, jež může být po opuštění podprogramu přepsán. Jednoduchý způsob, jak rezervovat požadovaný prostor předem dané velikosti, je posunout ukazatel na vrchol zásobníku a adresy v rozsahu mezi novým a starým vrcholem používat pro hodnoty lokálních proměnných. +more Je to výrazně rychlejší než jiné způsoby dynamické přidělování paměti a opět to automaticky podporuje reentrantnost a také rekurzi - při každém novém vstupu do podprogramu je zarezervován zvláštní prostor pro lokální proměnné. Předávání parametrů podprogramu: Z volající části kódu je často volaným podprogramům potřeba předat nějaké parametry. Pokud je parametrů jen málo, je možné (a některé překladače takovou možnost nabízí) předat parametry v registrech procesoru, pokud je jich však více, je potřeba předat je v paměti. Opět se zde nabízí posunutí vrcholu zásobníku a uložení parametrů do vzniklého prostoru. Uložení ukazatele na instanci objektu: V objektově orientovaném programování jsou obvykle podprogramy přiřazeny ke konkrétní instanci třídy. Některé objektově orientované jazyky, například C++, informaci o této příslušnosti udržují tak, že umístěním na zásobník předávají podprogramům jako zvláštní parametr ukazatel na patřičnou instanci. Vyhodnocování výrazů: Operandy a meziprodukty aritmetických a logických operací jsou obvykle udržovány v registrech procesoru, ale může dojít k situaci, že operandů je moc a registrů je málo. V takových případech může překladač odkládat některé operandy na zásobník (výpočet pak připomíná postfixovou notaci). Zpřístupnění kontextu u vnořených funkcí: Některé programovací jazyky (například Pascal nebo Ada) podporují vnořené podprogramy, kdy kód z vnitřního podprogramu má přístup k lokálním proměnným vnějšího podprogramu. To může být implementováno například tak, že kromě samotných lokálních proměnných se na zásobníku při volání předává také ukazatel na místo uložení lokálních proměnných nadřazené funkce. Ukládání širšího kontextu: V některých situacích je potřeba uchovat přes volání podprogramu víc hodnot, než jen adresu příští instrukce, například i jiné registry. V takových případech mohou být i ony uloženy na zásobník. Typickým příkladem je obsluha přerušení, při které je zapotřebí uchovat například obsah registru příznaků.

Příklad struktury zásobníku volání

Záznamy na zásobníku se skládají z jednotlivých rámců odpovídajících jednotlivým neukončeným voláním. Konkrétní podoba rámců je dána zejména architekturou.

Například na některých architekturách roste zásobník směrem nahoru (další záznamy jsou ukládány na místa s vyšší adresou), na jiných směrem dolů (další záznamy jsou ukládány na místa s nižší adresou)..

Pokud například byla zavolána funkce DrawLine z funkce DrawSquare, pak může vrchol zásobníku vypadat takto: střed|

Rámec na vrcholu zásobníku patří k současnosti vykonávanému podprogramu a obvykle obsahuje minimálně následující položky: * parametry předané funkci (jsou-li) * návratová adresa * rezervovaný prostor pro lokální proměnné (jsou-li)

Ukazatele na zásobníkové rámce

Na vrchol zásobníku vždy ukazuje speciální registr, ukazatel zásobníku ( SP). Jeho hodnota se ovšem mění nejen při vstupu do funkce či při návratu z ní, ale může se měnit i v době běhu (například je-li zásobník použit při aritmetických výpočtech, nebo v okamžiku, když už jsou do něj připravovány parametry pro dále volanou funkci). +more Obvyklým postupem je tedy na začátku podprogramu uložit hodnotu ukazatele zásobníku do jiného zvláštního registru, kde se pak uchovává v nezměněné podobě po celou dobu běhu funkce.

Velikost rámců na zásobníku

Protože na zásobník jsou ukládány parametry podprogramů, vyžadují různé podprogramy různě veliké rámce v závislosti na počtu a velikosti svých parametrů. Překladače vyšších programovacích jazyků dnes navíc často umožňují alokovat různě veliké lokální proměnné v závislosti na parametrech, takže dokonce různá volání téže funkce mohou vyžadovat jiné množství prostoru, a to takové, které je v době kompilace programu neznámé. +more To je také dalším důvodem, proč je využíván ukazatel na rámec, který ukazuje na spodek rámce - z ukazatele na vrchol rámce nelze bez znalosti velikosti parametrů zjistit adresy jednotlivých parametrů.

Ukazatel na rámec volajícího

Na většině systémů je obvyklé mít jako součást rámce aktuálního podprogramu uložený také ukazatel na rámec volajícího podprogramu. To je především proto, že ukazatel na rámec volajícího podprogramu patří mezi lokální hodnoty toho kterého podprogramu a tedy nemůže být uchován v registrech, ale je zapotřebí ho při zavolání podprogramu někam uložit a při ukončení podprogramu opět obnovit. +more Zároveň to může usnadnit ladění programu, neboť je kdykoliv možné snadno a rychle zjistit posloupnost volání.

Překryv rámců

Různé překladače postupují při alokování prostoru pro předání parametrů různě. Některé vždy před každým voláním alokují potřebný prostor pro parametry toho kterého podprogramu, jiné na začátku podprogramu rovnou alokují potřebný prostor pro všechny podprogramy, které je možno dále volat. +more Pak je takový prostor pokládán za překryv rámců.

Použití

Operace na straně volajícího

Na straně volajícího je potřeba v rámci volání obvykle vykonat jen minimum explicitně prováděných činností (což je dobře, protože jedna funkce může být volána mnohokrát z různých míst). Jsou zde pochopitelně vypočítány hodnoty jednotlivých parametrů volání a ty jsou poté umístěny na zásobník (případně do registrů, v závislosti na použité konvenci volání). +more Pak už je provedena instrukce volání.

Operace při vstupu do podprogramu

Na začátku podprogramu je obvykle proveden takzvaný prolog podprogramu, který zajišťuje nutný servis než začne provádění samotného podprogramu.

Na začátku obvykle dojde k uložení některých hodnot na zásobník, bývá ukládána návratová adresa a také hodnota ukazatele na zásobník a ukazatele na rámec. Na některých architekturách se ovšem o toto vše automaticky postará instrukce volání a není třeba to provádět v rámci prologu.

Dalším krokem je aktualizace ukazatele na rámec (je-li používán) na hodnotu ukazatele na zásobník. Pak je hodnota ukazatele na zásobník změněna, aby tak byl rezervován prostor pro lokální proměnné.

Operace při návratu z podprogramu

Podprogram před návratem řízení do volají funkce obvykle provede soubor příkazů, které jsou opakem kroků při vstupu do podprogramu. Obnoví se uložené hodnoty registrů, změní se hodnota ukazatele (tedy dealokují se lokální proměnné) a pak se zavolá instrukce návratu, která nahraje ze zásobníku návratovou adresu do instrukčního ukazatele.

Někdy se také ze zásobníku uvolní prostor pro parametry podprogramu, jindy se o uvolnění tohoto prostoru stará až volající funkce, když je jí navráceno řízení.

Další souvislosti

Aktuální data ze zásobníku mohou být použita kromě ladění také k profilování.

Zároveň je také zásobník tradičním místem pro bezpečnostní útoky. V jazycích, které nekontrolují zápis mimo meze pole, totiž zápisem za hranici lokálního pole lze přepsat návratovou adresu a tak přinutit program předat řízení útočníkovi. +more Jedná se o takzvané přetečení na zásobníku.

5 min read
Share this post:
Like it 8

Leave a Comment

Please, enter your name.
Please, provide a valid email address.
Please, enter your comment.
Enjoy this post? Join Cesko.wiki
Don’t forget to share it
Top