Las pilas nos permiten omitir con elegancia los límites impuestos por el número finito de registros.
Imagine tener exactamente 26 "registros az" globales (o incluso tener solo los registros de 7 bytes del chip 8080) Y cada función que escriba en esta aplicación comparte esta lista plana.
Un comienzo ingenuo sería asignar los primeros registros a la primera función, y sabiendo que solo tomó 3, comience con "d" para la segunda función ... Se acaba rápidamente.
En cambio, si tiene una cinta metafórica, como la máquina de Turing, puede hacer que cada función inicie una "llamada a otra función" guardando todas las variables que está usando y reenvíe () la cinta, y luego la función de llamada puede confundirse con tantas se registra como quiere. Cuando finaliza la llamada, devuelve el control a la función principal, que sabe dónde enganchar la salida de la persona que llama, según sea necesario, y luego reproduce la cinta hacia atrás para restaurar su estado.
Su marco de llamada básico es solo eso, y se crean y descartan mediante secuencias de código de máquina estandarizadas que el compilador realiza alrededor de las transiciones de una función a otra. (Ha pasado mucho tiempo desde que tuve que recordar mis marcos de pila C, pero puedes leer sobre las diversas formas en que las tareas de quién deja qué en X86_calling_conventions ).
(la recursividad es increíble, pero si alguna vez hubiera tenido que hacer malabarismos con los registros sin una pila, realmente apreciaría las pilas).
Supongo que el aumento del espacio en el disco duro y la RAM necesarios para almacenar el programa y admitir su compilación (respectivamente) es la razón por la que utilizamos pilas de llamadas. ¿Es eso correcto?
Si bien podemos incorporar más en estos días, ("más velocidad" siempre es buena; "menos kb de ensamblaje" significa muy poco en un mundo de transmisiones de video) La principal limitación está en la capacidad del compilador de aplanar ciertos tipos de patrones de código.
Por ejemplo, objetos polimórficos: si no conoce el único tipo de objeto que recibirá, no puede aplanarlo; debe mirar la tabla de características del objeto y llamar a través de ese puntero ... trivial para hacer en tiempo de ejecución, imposible de alinear en tiempo de compilación.
Una cadena de herramientas moderna puede alinear felizmente una función definida polimórficamente cuando ha aplanado lo suficiente a la persona que llama para saber exactamente qué sabor de obj es:
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);
}
en lo anterior, el compilador puede optar por mantenerse en línea estática, desde InlineMe a través de cualquier acción interna (), ni la necesidad de tocar ninguna vtables en tiempo de ejecución.
Sin embargo, cualquier incertidumbre en lo que el sabor del objeto se deja como una llamada a una función discreta, aunque algunas otras invocaciones de la misma función están entre líneas.