Stosy pozwalają nam elegancko ominąć ograniczenia narzucone przez skończoną liczbę rejestrów.
Wyobraź sobie, że masz dokładnie 26 globalnych „rejestrów az” (lub nawet posiadasz tylko 7-bajtowych rejestrów układu 8080). I każda funkcja, którą piszesz w tej aplikacji, dzieli tę płaską listę.
Naiwnym początkiem byłoby przydzielenie kilku pierwszych rejestrów do pierwszej funkcji i wiedząc, że zajęło to tylko 3, zacznij od „d” dla drugiej funkcji… Szybko skończysz.
Zamiast tego, jeśli masz metaforyczny taśmę, jak maszyna Turinga, można mieć każda funkcja rozpocząć „wywołać inną funkcję” zapisując wszystkie zmienne to korzystania i forward () taśmę, a następnie funkcja wywoływany może radzić z tak wielu rejestruje się, jak chce. Po zakończeniu procesu odbiorca zwraca kontrolę funkcji nadrzędnej, która wie, gdzie w razie potrzeby zaczepić wyjście odbiorcy, a następnie odtwarza taśmę do tyłu, aby przywrócić jej stan.
Podstawowa ramka wywołania jest właśnie taka i jest tworzona i upuszczana przez znormalizowane sekwencje kodów maszynowych, które kompilator umieszcza wokół przejść z jednej funkcji do drugiej. (Minęło dużo czasu, odkąd musiałem pamiętać moje ramki stosu C, ale możesz przeczytać o różnych sposobach, które spadają z tego, co na X86_calling_conventions .)
(rekurencja jest niesamowita, ale jeśli kiedykolwiek musiałbyś żonglować rejestrami bez stosu, naprawdę doceniłbyś stosy).
Podejrzewam, że zwiększone miejsce na dysku twardym i pamięć RAM potrzebne do przechowywania programu i obsługi jego kompilacji (odpowiednio) są powodem, dla którego używamy stosów wywołań. Czy to jest poprawne?
Chociaż możemy teraz wstawiać więcej, („większa szybkość” jest zawsze dobra; „mniej kb zestawu” oznacza bardzo mało w świecie strumieni wideo) Głównym ograniczeniem jest zdolność kompilatora do spłaszczania określonych typów wzorców kodu.
Na przykład obiekty polimorficzne - jeśli nie znasz jedynego rodzaju obiektu, który zostanie ci przekazany, nie możesz spłaszczyć; musisz spojrzeć na tabelę funkcji obiektu i wywołać ten wskaźnik ... trywialne do wykonania w czasie wykonywania, niemożliwe do wstawienia w czasie kompilacji.
Nowoczesny łańcuch narzędzi może z radością wprowadzać funkcję zdefiniowaną polimorficznie, gdy spłaszczy już tyle osób wywołujących, aby dokładnie wiedzieć , jaki jest smak obiektu obj:
class Base {
public: void act() = 0;
};
class Child1: public Base {
public: void act() {};
};
void ActOn(Base* something) {
something->act();
}
void InlineMe() {
Child1 thingamabob;
ActOn(&thingamabob);
}
powyżej kompilator może wybrać statyczne wstawianie, od InlineMe poprzez cokolwiek, co jest w środku act (), ani nie trzeba dotykać żadnych tabel w czasie wykonywania.
Ale jakakolwiek niepewność w jaki smak obiektu pozostawi je jako wezwanie do dyskretnej funkcji, nawet jeśli niektóre inne wywołania tej samej funkcji są wstawiane.