Gli stack ci permettono di aggirare elegantemente i limiti imposti dal numero finito di registri.
Immagina di avere esattamente 26 globals "registers a-z" (o anche di avere solo i registri di dimensioni 7 byte del chip 8080)
E ogni funzione che scrivi in questa app condivide questo elenco semplice.
Un inizio ingenuo sarebbe quello di allocare i primi registri alla prima funzione, e sapendo che ci sono voluti solo 3, inizia con "d" per la seconda funzione ...
Corri velocemente.
Invece, se hai un metaphorical tape, come il turing machine, potresti avere ciascuna funzione avviare una "call another function" salvando tutte le variabili che è usando e forward () il nastro, e quindi la funzione del callee può confondersi con tutti i registri che vuole. Quando il callee è finito, restituisce il controllo alla funzione genitore, che sa dove intrappolare l'uscita del callee secondo necessità, e quindi riproduce il nastro all'indietro per ripristinarne lo stato.
Il frame di chiamata di base è proprio questo, e vengono creati e rilasciati da sequenze di codice macchina standardizzate che il compilatore inserisce attorno alle transizioni da una funzione all'altra. (È passato molto tempo da quando ho dovuto ricordare i miei frame stack di C, ma puoi leggere i vari modi in cui i doveri di chi rilascia cosa a X86_calling_conventions .)
(la ricorsione è fantastica, ma se avessi mai dovuto manipolare i registri senza uno stack, allora davvero apprezzerai gli stack.)
I suppose the increased hard disk space and RAM needed to store the program and support its compilation (respectively) is the reason why we use call stacks. Is that correct?
Mentre possiamo inline di più in questi giorni, ("più velocità" è sempre buona; "meno kb di assembly" significa molto poco in un mondo di flussi video)
La principale limitazione è nella capacità del compilatore di appiattirsi su determinati tipi di pattern di codice.
Ad esempio, oggetti polimorfici - se non conosci l'unico tipo di oggetto che ti verrà consegnato, non puoi appiattirlo; devi guardare il vtable delle caratteristiche dell'oggetto e chiamare attraverso quel puntatore ... banale da fare in fase di runtime, impossibile da allineare in fase di compilazione.
Una moderna toolchain può felicemente inline una funzione definita polimorficamente quando ha appiattito abbastanza dei chiamanti per sapere esattamente quale sapore di 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);
}
in quanto sopra, il compilatore può scegliere di rimanere drasticamente in linea, da InlineMe a qualunque cosa sia all'interno di act (), né la necessità di toccare qualsiasi vtables in fase di runtime.
Ma qualsiasi incertezza in quale sapore di oggetto lo lascerà come una chiamata a una funzione discreta, anche se alcune altre invocazioni della stessa funzione sono in linea.