Expliquez le concept d'un cadre de pile en un mot

200

Il semble que j'ai l'idée de la pile d'appels dans la conception d'un langage de programmation. Mais je ne trouve pas (probablement, je ne cherche pas assez fort) une explication décente de ce qu'est le cadre de pile .

Je voudrais donc demander à quelqu'un de me l'expliquer en quelques mots.

ikostia
la source

Réponses:

195

Un cadre de pile est un cadre de données qui est poussé sur la pile. Dans le cas d'une pile d'appels, une trame de pile représenterait un appel de fonction et ses données d'argument.

Si je me souviens bien, l'adresse de retour de la fonction est d'abord poussée sur la pile, puis les arguments et l'espace pour les variables locales. Ensemble, ils font le «cadre», bien que cela dépende probablement de l'architecture. Le processeur sait combien d'octets se trouvent dans chaque trame et déplace le pointeur de pile en conséquence lorsque les trames sont poussées et sautées hors de la pile.

ÉDITER:

Il existe une grande différence entre les piles d'appels de niveau supérieur et la pile d'appels du processeur.

Lorsque nous parlons de la pile d'appels d'un processeur, nous parlons de travailler avec des adresses et des valeurs au niveau octet / mot dans l'assembly ou le code machine. Il existe des «piles d'appels» lorsque l'on parle de langages de niveau supérieur, mais ce sont des outils de débogage / d'exécution gérés par l'environnement d'exécution afin que vous puissiez enregistrer ce qui s'est mal passé avec votre programme (à un niveau élevé). À ce niveau, des choses comme les numéros de ligne et les noms de méthode et de classe sont souvent connues. Au moment où le processeur obtient le code, il n'a absolument aucune idée de ces choses.

Tony R
la source
6
"Le processeur sait combien d'octets se trouvent dans chaque trame et déplace le pointeur de pile en conséquence lorsque les trames sont poussées et sautées hors de la pile." - Je doute que le processeur sache quoi que ce soit sur la pile, car NOUS le manipulons via la sous-attribution (allocation), la poussée et l'éclatement. Et voici donc des conventions d'appel qui expliquent comment utiliser la pile.
Victor Polevoy
78

Si vous comprenez très bien la pile, vous comprendrez comment fonctionne la mémoire dans le programme et si vous comprenez comment la mémoire fonctionne dans le programme, vous comprendrez comment le magasin de fonctions dans le programme et si vous comprenez comment le magasin de fonctions dans le programme, vous comprendrez comment fonctionne la fonction récursive et si vous comprenez comment fonctionne la fonction récursive, vous comprendrez comment fonctionne le compilateur et si vous comprenez comment fonctionne le compilateur, votre esprit fonctionnera comme compilateur et vous déboguerez n'importe quel programme très facilement

Permettez-moi d'expliquer comment fonctionne la pile:

Vous devez d'abord savoir comment les fonctions sont représentées dans la pile:

Le tas stocke les valeurs allouées dynamiquement.
La pile stocke les valeurs d'allocation et de suppression automatiques.

entrez la description de l'image ici

Comprenons par exemple:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Comprenez maintenant certaines parties de ce programme:

entrez la description de l'image ici

Voyons maintenant ce qu'est la pile et quelles sont les pièces de la pile:

entrez la description de l'image ici

Attribution de la pile:

N'oubliez pas une chose: si la condition de retour d'une fonction est satisfaite, qu'elle ait chargé les variables locales ou non, elle reviendra immédiatement de la pile avec son cadre de pile. Cela signifie que chaque fois qu'une fonction récursive obtient la condition de base satisfaite et que nous mettons un retour après la condition de base, la condition de base n'attendra pas pour charger les variables locales qui se trouvent dans la partie "else" du programme. Il renverra immédiatement l'image actuelle de la pile à la suite de laquelle l'image suivante est maintenant dans l'enregistrement d'activation.

Voir cela en pratique:

entrez la description de l'image ici

Désallocation du bloc:

Alors maintenant, chaque fois qu'une fonction rencontre l'instruction return, elle supprime le cadre actuel de la pile.

Lors du retour de la pile, les valeurs seront retournées en sens inverse de l'ordre d'origine dans lequel elles ont été allouées dans la pile.

entrez la description de l'image ici

Aaditya Ura
la source
3
la pile croît vers le bas et le tas croît vers le haut, vous les avez inversés dans votre diagramme. DIAGRAMME CORRECT ICI
Rafael
@Rafael désolé pour la confusion, je parlais de la direction de la croissance, je ne parlais pas de la direction de la croissance de la pile. Il existe une différence entre le sens de croissance et le sens de croissance de la pile. Voir ici stackoverflow.com/questions/1677415/…
Aaditya Ura
2
Rafael a raison. De plus, la première image est fausse. Remplacez-le par autre chose (recherchez dans Google Images «pile de tas»).
Nikos
Donc, si je comprends bien, dans votre troisième diagramme, il y a 3 cadres de pile car hello()a appelé récursivement hello()qui a ensuite (encore) récursivement appelé hello(), et le cadre global est la fonction d'origine qui a appelé le premier hello()?
Andy J
1
Où les liens nous mènent-ils ?? Par souci sérieux de sécurité, ces liens devraient être supprimés dès que possible.
Shivanshu
45

Un résumé rapide. Peut-être que quelqu'un a une meilleure explication.

Une pile d'appels est composée de 1 ou plusieurs trames de pile. Chaque trame de pile correspond à un appel à une fonction ou une procédure qui ne s'est pas encore terminée par un retour.

Pour utiliser un cadre de pile, un thread conserve deux pointeurs, l'un s'appelle le pointeur de pile (SP) et l'autre s'appelle le pointeur de cadre (FP). SP pointe toujours vers le "haut" de la pile, et FP pointe toujours vers le "haut" du cadre. De plus, le thread maintient également un compteur de programmes (PC) qui pointe vers la prochaine instruction à exécuter.

Les éléments suivants sont stockés sur la pile: variables locales et temporelles, paramètres réels de l'instruction courante (procédure, fonction, etc.)

Il existe différentes conventions d'appel concernant le nettoyage de la pile.

ervinbosenbacher
la source
7
N'oubliez pas que l'adresse de retour du sous-programme va sur la pile.
Tony R
4
Frame Pointer est également Base Pointer en termes x86
peterchaula
1
Je voudrais souligner qu'un pointeur de trame pointe vers le début de la trame de pile pour l'incarnation de la procédure actuellement active.
Serveur Khalilov
13

"Une pile d'appels est composée de trames de pile ..." -  Wikipedia

Un cadre de pile est une chose que vous mettez sur la pile. Ce sont des structures de données qui contiennent des informations sur les sous-programmes à appeler.

Waleed Khan
la source
Désolé, je n'ai aucune idée de comment j'ai raté ça sur wiki. Merci. Dois-je bien comprendre que, dans les langages dynamiques, la taille du cadre n'est pas une valeur constante car les paramètres locaux de la fonction ne sont pas exactement connus?
ikostia
La taille et la nature d'un châssis dépendent fortement de l'architecture de la machine. En fait, le paradigme même d'une pile d'appels est spécifique à l'architecture. Pour autant que je sache, c'est toujours variable car différents appels de fonction auront différentes quantités d'arguments.
Tony R
Notez que la taille du cadre de pile doit être connue du processeur lors de sa manipulation. Lorsque cela se produit, la taille des données est déjà déterminée. Les langages dynamiques sont compilés en code machine tout comme les langages statiques, mais sont souvent exécutés juste à temps afin que le compilateur puisse maintenir le dynamisme et que le processeur puisse travailler avec des tailles de trame "connues". Ne confondez pas les langages de niveau supérieur avec le code / assemblage de la machine, c'est là que ces choses se produisent réellement.
Tony R
Eh bien, mais les langages dynamiques ont aussi leurs piles d'appels, n'est-ce pas? Je veux dire, si, disons, Python veut exécuter une procédure, les données sur cette procédure sont stockées à l'intérieur de la structure d'un interpréteur Python, ai-je raison? Je veux donc dire que la pile d'appels est présente non seulement à un faible niveau.
ikostia
Après avoir lu un peu de cet article wikipedia, je me tiens corrigé (un peu). La taille du cadre de pile peut rester inconnue au moment de la compilation . Mais au moment où le processeur travaille avec des pointeurs pile + trame, il doit savoir quelles sont les tailles. La taille peut être variable mais le processeur connaît la taille, c'est ce que j'essayais de dire.
Tony R
5

Les programmeurs peuvent avoir des questions sur les trames de pile non pas dans un sens large (qu'il s'agit d'une entité unique dans la pile qui sert un seul appel de fonction et conserve l'adresse de retour, les arguments et les variables locales) mais au sens étroit - lorsque le terme stack frames est mentionné dans contexte des options du compilateur.

Que l'auteur de la question le veuille ou non, mais le concept d'un cadre de pile du point de vue des options du compilateur est une question très importante, non couverte par les autres réponses ici.

Par exemple, le compilateur C / C ++ Microsoft Visual Studio 2015 a l'option suivante liée à stack frames:

  • / Oy (Omission Frame-Pointer)

GCC dispose des éléments suivants:

  • -fomit-frame-pointer (Ne pas conserver le pointeur de trame dans un registre pour les fonctions qui n'en ont pas besoin. Cela évite les instructions pour enregistrer, configurer et restaurer les pointeurs de trame; cela rend également un registre supplémentaire disponible dans de nombreuses fonctions )

Le compilateur Intel C ++ possède les éléments suivants:

  • -fomit-frame-pointer (Détermine si EBP est utilisé comme registre à usage général dans les optimisations)

qui a l'alias suivant:

  • / Oy

Delphi a l'option de ligne de commande suivante:

  • - $ W + (Générer des cadres de pile)

Dans ce sens spécifique, du point de vue du compilateur, un cadre de pile n'est que le code d'entrée et de sortie de la routine , qui pousse une ancre vers la pile - qui peut également être utilisée pour le débogage et pour la gestion des exceptions. Les outils de débogage peuvent analyser les données de la pile et utiliser ces ancres pour le retour en arrière, tout en les localisant call sitesdans la pile, c'est-à-dire pour afficher les noms des fonctions dans l'ordre où elles ont été appelées hiérarchiquement. Pour l'architecture Intel, c'est push ebp; mov ebp, espsoit enterpour l'entrée et mov esp, ebp; pop ebpsoit leavepour la sortie.

C'est pourquoi il est très important de comprendre pour un programmeur ce qu'est un cadre de pile en ce qui concerne les options du compilateur - car le compilateur peut contrôler s'il faut générer ce code ou non.

Dans certains cas, le cadre de pile (code d'entrée et de sortie pour la routine) peut être omis par le compilateur, et les variables seront directement accessibles via le pointeur de pile (SP / ESP / RSP) plutôt que le pointeur de base pratique (BP / ESP / RSP). Conditions d'omission du cadre de pile, par exemple:

  • la fonction est une fonction feuille (c'est-à-dire une entité finale qui n'appelle pas d'autres fonctions);
  • il n'y a pas de constructions try / finally ou try / except ou similaires, c'est-à-dire qu'aucune exception n'est utilisée;
  • aucune routine n'est appelée avec des paramètres sortants sur la pile;
  • la fonction n'a pas de paramètres;
  • la fonction n'a pas de code d'assemblage en ligne;
  • etc...

L'omission de trames de pile (code d'entrée et de sortie pour la routine) peut rendre le code plus petit et plus rapide, mais cela peut également affecter négativement la capacité des débogueurs à retracer les données de la pile et à les afficher au programmeur. Ce sont les options du compilateur qui déterminent dans quelles conditions une fonction doit avoir le code d'entrée et de sortie, par exemple: (a) toujours, (b) jamais, (c) si nécessaire (en spécifiant les conditions).

Maxim Masiutin
la source
-1

La trame de pile est l'information emballée liée à un appel de fonction. Ces informations incluent généralement des arguments passés à la fonction, des variables locales et où retourner à la fin. L'enregistrement d'activation est un autre nom pour un cadre de pile. La disposition du cadre de pile est déterminée dans l'ABI par le fabricant et chaque compilateur prenant en charge l'ISA doit être conforme à cette norme, mais le schéma de disposition peut dépendre du compilateur. Généralement, la taille de trame de pile n'est pas limitée mais il existe un concept appelé "zone rouge / protégée" pour permettre aux appels système ... etc de s'exécuter sans interférer avec une trame de pile.

Il y a toujours un SP mais sur certains ABI (ARM et PowerPC par exemple) FP est optionnel. Les arguments qui devaient être placés sur la pile peuvent être compensés à l'aide du SP uniquement. La génération ou non d'un cadre de pile pour un appel de fonction dépend du type et du nombre d'arguments, des variables locales et de la manière dont les variables locales sont généralement accessibles. Sur la plupart des ISA, tout d'abord, des registres sont utilisés et s'il y a plus d'arguments que de registres dédiés à passer des arguments, ils sont placés sur la pile (par exemple, x86 ABI a 6 registres pour passer des arguments entiers). Par conséquent, parfois, certaines fonctions n'ont pas besoin d'un cadre de pile pour être placées sur la pile, juste l'adresse de retour est poussée sur la pile.

théière ivre
la source