Comprendre le cadre de pile de l'appel de fonction en C / C ++?

19

J'essaie de comprendre comment les cadres de pile sont construits et quelles variables (paramètres) sont poussées pour s'empiler dans quel ordre? Certains résultats de recherche ont montré que le compilateur C / C ++ décide en fonction des opérations effectuées dans une fonction. Par exemple, si la fonction était censée simplement incrémenter une valeur int transmise de 1 (similaire à l'opérateur ++) et la renvoyer, elle placerait tous les paramètres de la fonction et les variables locales dans des registres.

Je me demande quels registres sont utilisés pour les paramètres renvoyés ou passés par valeur. Comment les références sont-elles retournées? Comment le compilateur choisit-il entre eax, ebx, ecx et edx?

Que dois-je savoir pour comprendre comment les registres, les références de pile et de tas sont utilisés, créés et détruits lors des appels de fonction?

Gana
la source
c'est assez difficile à lire (mur de texte). Pourriez-vous modifier votre article sous une meilleure forme?
moucher
1
Cette question me semble assez large. De plus, cela ne sera-t-il pas très spécifique à la plate-forme?
Kazark
Une question a également été posée sur SO: stackoverflow.com/questions/16088040/…
Wayne Conrad
Voir aussi ma réponse sur SO
Basile Starynkevitch

Réponses:

11

En plus de ce que Dirk a dit, une utilisation importante des trames de pile est de sauvegarder les valeurs précédentes des registres afin qu'ils puissent être restaurés après un appel de fonction. Ainsi, même sur les processeurs où des registres sont utilisés pour transmettre des paramètres, renvoyer une valeur et enregistrer l'adresse de retour, les valeurs de ces registres sont enregistrées sur la pile avant un appel de fonction afin de pouvoir être restaurées après l'appel. Cela permet à une fonction d'en appeler une autre sans écraser ses propres paramètres ni oublier sa propre adresse de retour.

Ainsi, appeler une fonction B à partir de la fonction A sur un système "générique" typique peut impliquer les étapes suivantes:

  • fonction A:
    • pousser l'espace pour la valeur de retour
    • paramètres de poussée
    • pousser l'adresse de retour
  • passer à la fonction B
  • fonction B:
    • pousser l'adresse de la trame de pile précédente
    • pousser les valeurs des registres que cette fonction utilise (pour pouvoir les restaurer)
    • pousser l'espace pour les variables locales
    • faire le calcul nécessaire
    • restaurer les registres
    • restaurer le cadre de pile précédent
    • stocker le résultat de la fonction
    • sauter à l'adresse de retour
  • fonction A:
    • pop les paramètres
    • pop la valeur de retour

Ce n'est en aucun cas la seule façon dont les appels de fonction peuvent fonctionner (et j'ai peut-être une étape ou deux en panne), mais cela devrait vous donner une idée de la façon dont la pile est utilisée pour permettre au processeur de gérer les appels de fonction imbriqués.

Caleb
la source
Que signifie exactement "pousser" ici? Je ne sais pas quoi en penser.
Tomáš Zato - Rétablir Monica le
2
@ TomášZato pushet popsont les deux opérations fondamentales sur une pile. Une pile est une structure de dernier entré, premier sorti, comme une pile de livres. Lorsque vous push, vous placez un nouvel objet au-dessus de la pile; lorsque vous popprenez un objet du haut de la pile. Vous n'êtes pas autorisé à insérer ou supprimer des objets au milieu, vous ne pouvez opérer que sur le dessus de la pile. Vous pouvez en savoir plus sur les piles en général et la pile de programmes en particulier sur Wikipedia.
Caleb
11

Cela dépend de la convention d'appel utilisée. Celui qui définit la convention d'appel peut prendre cette décision comme bon lui semble.

Dans la convention d'appel la plus courante sur x86, les registres ne sont pas utilisés pour transmettre des paramètres; les paramètres sont poussés sur la pile en commençant par le paramètre le plus à droite. La valeur de retour est placée dans eax et peut utiliser edx si elle a besoin de l'espace supplémentaire. Les références et les pointeurs sont tous deux retournés sous la forme d'une adresse en eax.

Dirk Holsopple
la source
5

Si vous comprenez très bien la pile, vous comprendrez comment la mémoire fonctionne 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 stocker les fonctions dans la pile:

Stockage dynamique des valeurs d'allocation de mémoire dynamique. Stockez 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:

Souvenez-vous d'une chose si une fonction obtient un «retour», qu'elle ait chargé toutes ses variables locales ou quoi qu'elle revienne immédiatement de la pile dans son cadre de pile. Cela signifie que lorsqu'une fonction récursive obtient la condition de base et que nous mettons return après la condition de base afin que la condition de base n'attende pas pour charger les variables locales qui sont situées dans la partie "else" du programme, elle retournera immédiatement l'image actuelle de la pile et maintenant si une image retourner l'image suivante est dans le dossier d'activation. Voir ceci en pratique:

entrez la description de l'image ici

Désallocation du bloc:

Alors maintenant, chaque fois qu'une fonction trouve l'instruction return, elle supprime l'image courante de la pile.

en revenant de la valeur de pile retournera dans l'ordre inverse de l'ordre dans lequel ils ont alloué dans la pile.

entrez la description de l'image ici

Ce sont des descriptions très courtes et si vous voulez en savoir plus sur la pile et la double récursivité, lisez deux articles de ce blog:

En savoir plus sur la pile étape par étape

En savoir plus sur la double récursivité pas à pas avec la pile

user5904928
la source
3

Ce que vous recherchez s'appelle Application Binary Interface - ABI.

Il existe une spécification pour chaque compilateur qui définit l'ABI.

Chaque plate-forme spécifie généralement et ABI afin de prendre en charge l'interopérabilité entre les compilateurs. Par exemple, les conventions d'appel x86 expliquent les conventions d'appel typiques pour x86 et x86-64. J'attendrais cependant un document plus officiel que wikipedia.

Le projet de loi porte
la source