Poprzedni :: Spis treści :: Następny
2. Podstawowe informacje
2.1 Organizacja pamięci programu
2.2 Stos
2.3 Mechanizm wywoływania funkcji
2.3.1 Instrukcja CALL
2.3.2 Instrukcja RET
Pamięć procesu przedstawia następujący rysunek:
Poszczególne fragmenty pamięci zawierają:
Stos jest to ciągły obszar pamięci implementujący kolejkę FILO (pierwszy
wchodzi ostatni wychodzi). Operacje na stosie (push, pop) dostarczane są
przez odpowiednie instrukcje procesora (odpowiednio PUSH i POP). Stos jest
ściśle związany z mechanizmem wywoływania funkcji i przechowuje tzw. ramki
umieszczanych tam w momencie wywołania funkcji i zdejmowanych w chwili powrotu
z funkcji. Każda ramka zawiera następujące elementy:
Dodatkowo należy zwrócić uwagę, że na maszynach typu x86 stos rośnie od
adresów wysokich ku niższym (najwyższy element na stosie ma najniższy adres).
Przykładowy wierzchołek stosu przedstawia poniższy rysunek:
Rysunek, oprócz przykładowej ramki, przedstawia także zawartość dwóch rejestrów procesora:
Dostępność rejestru EBP oraz pozycja na stosie jaką wskazuje pozwala na
odwoływanie się do argumentów funkcji oraz do jej zmiennych lokalnych poprzez
dodanie odpowiedniej wartości do EBP. Przykładowo:
EBP + 4 = adres powrotu EBP + 8 = pierwszy argument EBP - 4 = pierwsza zmienna lokalna funkcji
(wartościami dodawanymi są wielokrotności 4 ponieważ każdy element stosu ma długość jednego słowa a te ma 4 bajty (na maszynie x86).
Mechanizm umożliwiający wywoływanie funkcji musi zapewnić , że, po zakończeniu wykonywania wywoływanej funkcji, sterowanie wróci do miejsca wywołania. Aby to zapewnić procesory x86 udostępniają instrukcje CALL oraz RET (omówione poniżej). Dodatkowo na stos należy włożyć argumenty funkcji oraz zadbać o zapamiętanie adresu ramki funkcji wywołującej i odpowiednie uaktualnienie wskaźnika aktualnej ramki (rejestr EBP). O te czynności zadbać trzeba samodzielnie (oczywiście jeżeli programujemy w assemblerze, w przypadku języków wyższego poziomu odpowiednie operacje przeprowadza kompilator).
W przypadku języka C wywołanie funkcji odbywa się następująco:
Współczesne kompilatory starają się zoptymalizować (przyspieszyć) kod. Jedną z metod osiągnięcia tego jest powiększenie stosu (w momencie wykonywania kodu funkcji) o znacznie większą ilość komórek niż jest to potrzebne do przechowywania zmiennych lokalnych. Dodatkowa pamięć jest wykorzystywana np. do przyspieszenia funkcji. Na przykład zamiast używać instrukcji PUSH i POP, które wkładają odpowiedni element na stos i uaktualniają rejestr ESP, można "wkładać" elementy w odpowiednie miejsca poniżej wierzchołka stosu (oczywiście dbając o to aby nie uszkodzić znajdujących się tam danych) co można zrealizować przy pomocy jednej instrukcji.
Instrukcja CALL działa następująco:
Instrukcja RET działa następująco:
Poprzedni :: Spis treści :: Następny