Návrh procesoru a operačního systému už odedávna obsahuje stack. Pomocí něj můžeme rychle volat funkce a alokovat si paměť pro proměnné. Bez stacku by se naše programy mohly úplně obejít – a některé jazyky ho opravdu nepoužívají. Je vlastně na nás, jakým způsobem implementujeme volání funkcí, a kde si budeme chtít alokovat proměnné. Stack je ale široce používaná záležitost, kterou zaručeně v počítači máme, takže stojí za pozornost.

Stack

Každý proces při spuštění dostane přidělenou paměť, jakou si vyžádá. Na začátku je jeho programový kód, na konci bývá obvykle volné místo. Od konce tohoto volného místa směrem doleva se zapisují hodnoty na stack.

Pracujeme vždy s několika nejpozději přidanými hodnotami na stacku. To můžou být například proměnné, které jsme právě deklarovali. Dále za nimi jsou proměnné použité jinde v kódu, na které sahat nechceme a ani bychom neměli. Na nejnovější hodnotu ve stacku vždycky ukazuje stack pointer, obvykle uložený v registru RSP.

Pro práci se stackem slouží především instrukce PUSH a POP. Assemblerový příkaz PUSH 42 například sníží registr RSP o čtyři, a pak uloží čtyřbajtové číslo 42 do paměti na místo, kam právě RSP ukazuje. Assemblerový příkaz POP EAX přečte hodnotu z místa, kam odkazuje RSP, a pak zvýší RSP o čtyři.

Volací konvence

Stack nám umožňuje jednoduše dělit kód tak, aby se každá funkce mohla starat o svoje záležitosti. Před voláním funkce naskládáme její parametry na stack v pevném pořadí, aby s nimi mohla snadno pracovat. Jako poslední hodnotu na stack přidáme instruction pointer (vlastně se to stane automaticky), abychom se po skončení funkce dovedli vrátit k původní práci.

Funkce tedy ví, že první hodnota na stacku je instruction pointer (na něj by neměla sahat), za ním následuje několik pametrů (s těmi může dělat cokoliv) a dále jsou lvi (na ty se sahat nedoporučuje). S pamětí stacku dále vlevo může funkce zacházet libovolně, například si tam může ukládat svoje lokální proměnné.

Proměnné na stacku

Když potřebujeme místo na lokální proměnnou, například i uvnitř kódu for (int i=0; i<10; ++i), nejrychleji pro ni najdeme místo v paměti na stacku. Stačí zavolat PUSH a od té doby si pamatovat, že všechny dřívější proměnné jsme tím zamáčkli ve stacku o pár bajtů pod povrch. Všechny takové formality za nás naštěstí řeší kompilátor.

Debugger

Jakkoliv není slušné hrabat do stacku ostatním funkcím, může nám to dát užitečné informace o běhu programu. Například pokud program zamrzne nebo spadne, určitě nás zajímá, ve které funkci se to stalo, která funkce ji volala a tak dále. Všechny tyhle informace jsou ve stacku zapsané, stačí ho opatrně přečíst.

Zrychlení a zesložitění

Práce s pamětí je nevyhnutelně pomalá. Aby naše programy běžely co nejrychleji, kompilátor se snaží co nejvíc proměnných nechat přímo v registrech procesoru, a na stack je ukládá, až když mu nezbývá jiná možnost. To se týká i volání funkcí: dokud parametr funkce a její návratová hodnota nezabírají moc místa, smí být uložené přímo v registrech. Pochopitelně je nutné se pro takové zrychlení rozhodnout už během kompilace, aby volající kód i volaná funkce přesně věděly, jakým způsobem mají být hodnoty uložené.