Les piles nous permettent de contourner avec élégance les limites imposées par le nombre fini de registres.
Imaginez avoir exactement 26 globaux "registres az" (ou même n'avoir que les registres de la taille de la puce 8080 de la taille d'un octet) Et chaque fonction que vous écrivez dans cette application partage cette liste à plat.
Un début naïf serait d’attribuer les premiers registres à la première fonction, et sachant que cela ne prenait que 3, commencez par "d" pour la deuxième fonction ... Vous vous épuisez rapidement.
Par contre, si vous avez une bande métaphorique, comme la machine de lecture, vous pouvez faire en sorte que chaque fonction lance un "appel d’une autre fonction" en enregistrant toutes les variables qu’elle utilise et en transmettant la bande (), puis la fonction appelée peut s’embrouiller autant. s'inscrit comme il veut. Lorsque l'appelé est terminé, il renvoie le contrôle à la fonction parent, qui sait où capturer la sortie de l'appelé si nécessaire, puis lit la bande à l'envers pour restaurer son état.
Votre cadre d’appel de base n’est que cela. Il est créé et supprimé par des séquences de code machine normalisées que le compilateur insère autour des transitions d’une fonction à l’autre. (Cela fait longtemps que je ne devais plus me souvenir de mes cadres de pile C, mais vous pouvez lire les différentes manières dont les tâches de qui laisse quoi à X86_calling_conventions .)
(La récursivité est géniale, mais si vous aviez déjà à jongler avec des registres sans pile, vous apprécieriez vraiment les piles.)
Je suppose que l’augmentation de l’espace disque dur et de la mémoire RAM nécessaires pour stocker le programme et prendre en charge sa compilation (respectivement) est la raison pour laquelle nous utilisons des piles d’appels. Est-ce exact?
Bien que nous puissions davantage nous aligner ces temps-ci, ("plus de vitesse" est toujours bon; "moins de ko d'assemblage" signifie très peu dans un monde de flux vidéo). La principale limitation réside dans la capacité du compilateur à aplatir certains types de modèles de code.
Par exemple, les objets polymorphes - si vous ne connaissez pas le seul et unique type d'objet qui vous sera remis, vous ne pouvez pas l'aplatir; vous devez regarder la table des fonctionnalités de l'objet et appeler via ce pointeur ... trivial à faire au moment de l'exécution, impossible à aligner au moment de la compilation.
Une chaîne d’outils moderne peut heureusement intégrer une fonction définie par un polymorphisme quand elle a suffisamment écrasé le ou les appelants pour savoir exactement quelle est la saveur de 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);
}
Dans ce qui précède, le compilateur peut choisir de garder le droit d'inline alignée, d'InlineMe à tout ce qui se trouve à l'intérieur d'act (), sans avoir besoin de toucher les vtables au moment de l'exécution.
Mais toute incertitude quant à la nature de l'objet le laissera comme un appel à une fonction discrète, même si d'autres invocations de la même fonction sont en ligne .