As pilhas nos permitem ignorar elegantemente os limites impostos pelo número finito de registros.
Imagine ter exatamente 26 globais "registra az" (ou mesmo apenas os registros de 7 bytes do chip 8080) E todas as funções que você escreve neste aplicativo compartilham essa lista simples.
Um começo ingênuo seria alocar os primeiros registros para a primeira função e, sabendo que foram necessários apenas 3, inicie com "d" para a segunda função ... Você se esgota rapidamente.
Em vez disso, se você tiver uma fita metafórica, como a máquina de turing, cada função poderá iniciar uma "chamar outra função" salvando todas as variáveis que está usando e encaminhar () a fita e, em seguida, a função de chamada pode se confundir com tantas registra como deseja. Quando o chamado é retornado, ele retorna o controle para a função pai, que sabe onde capturar a saída do receptor conforme necessário e, em seguida, reproduz a fita para trás para restaurar seu estado.
Seu quadro de chamada básico é exatamente isso e é criado e descartado por seqüências de código de máquina padronizadas que o compilador realiza nas transições de uma função para outra. (Faz muito tempo que eu não lembro dos meus quadros de pilha C, mas você pode ler sobre as várias maneiras das tarefas de quem descarta o que em X86_calling_conventions .)
(a recursão é incrível, mas se você já teve que fazer malabarismos com registros sem uma pilha, apreciaria muito as pilhas.)
Suponho que o aumento do espaço no disco rígido e da RAM necessários para armazenar o programa e suportar sua compilação (respectivamente) seja a razão pela qual usamos pilhas de chamadas. Isso está correto?
Embora possamos alinhar mais atualmente, ("mais velocidade" é sempre bom; "menos kb de montagem" significa muito pouco em um mundo de fluxos de vídeo). A principal limitação está na capacidade do compilador de nivelar certos tipos de padrões de código.
Por exemplo, objetos polimórficos - se você não conhece o único e único tipo de objeto a ser entregue, não pode achatar; você precisa olhar para a tabela de recursos do objeto e chamar por esse ponteiro ... trivial para fazer em tempo de execução, impossível alinhar em tempo de compilação.
Uma cadeia de ferramentas moderna pode alinhar alegremente uma função definida polimorficamente quando achatar o (s) chamador (es) o suficiente para saber exatamente qual é o sabor do 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);
}
acima, o compilador pode optar por manter o status interno, desde o InlineMe até o que estiver dentro do ato (), nem a necessidade de tocar em nenhuma vtables no tempo de execução.
Mas qualquer incerteza quanto ao sabor do objeto o deixará como uma chamada para uma função discreta, mesmo que outras invocações da mesma função sejam incorporadas.