Comment fonctionne exactement de la pile des appels de travail?

Je vais essayer d'obtenir une compréhension plus profonde de la façon dont les opérations de bas niveau de la programmation des langues de travail, et en particulier la façon dont ils interagissent avec le système d'exploitation et PROCESSEUR. J'ai probablement lu toute les réponses dans chaque pile/tas de sujet ici sur Pile Dépassement de capacité, et ils sont tous brillants. Mais il y a encore une chose que je n'ai pas bien compris encore.

Considérer cette fonction dans le pseudo-code qui tend à être valide Rouille code 😉

fn foo() {
    let a = 1;
    let b = 2;
    let c = 3;
    let d = 4;

    //line X

    doSomething(a, b);
    doAnotherThing(c, d);
}

C'est la façon dont je me charge de la pile, de manière à ressembler à la ligne X:

Stack

a +-------------+
  | 1           | 
b +-------------+     
  | 2           |  
c +-------------+
  | 3           | 
d +-------------+     
  | 4           | 
  +-------------+ 

Maintenant, tout ce que j'ai lu sur la façon dont la pile fonctionne, c'est qu'il obéit strictement LIFO règles (last in, first out). Tout comme une pile de type de données dans .NET, Java ou tout autre langage de programmation.

Mais si c'est le cas, alors ce qui se passe après la ligne X? Parce que, évidemment, la prochaine chose que nous avons besoin est de travailler avec a et b, mais cela signifierait que le système d'exploitation et PROCESSEUR (?) a sortir des d et c premier à revenir à a et b. Mais alors qu'il allait tirer dans le pied, car il a besoin c et d dans la ligne suivante.

Alors, je me demande ce que exactement qui se passe en coulisses?

Une autre question connexe. Envisager de nous passer une référence à l'une des autres fonctions comme ceci:

fn foo() {
    let a = 1;
    let b = 2;
    let c = 3;
    let d = 4;

    //line X

    doSomething(&a, &b);
    doAnotherThing(c, d);
}

De la façon dont je comprends les choses, cela signifie que les paramètres de doSomething sont essentiellement pointant vers la même adresse mémoire comme a et b dans foo. Mais là encore, cela signifie qu'il n'y a pas de pop haut de la pile jusqu'à ce que nous arrivons à a et b passe.

Ces deux cas, me font penser que je n'ai pas bien saisi comment exactement la pile fonctionne et comment il se conforme strictement à l' LIFO règles.

  • Dernier entré, premier sorti uniquement les questions pour la réservation de l'espace sur la pile. Vous pouvez toujours accéder à toutes les variables qui est au moins sur votre cadre de pile (déclarée à l'intérieur de la fonction), même si c'est sous beaucoup d'autres variables
  • En d'autres termes, LIFO signifie que vous pouvez ajouter ou supprimer des éléments qu'à la fin de la pile, et vous pouvez toujours lire/modifier n'importe quel élément.
  • Pourquoi ne pas vous démonter une fonction simple, après compilation avec -O0 et de regarder les instructions générées? C'est joli, bien, instructif ;-). Vous verrez que le code fait bon usage de la R de la partie de la mémoire vive; elle accède à une adresse directement à volonté. Vous pouvez penser à un nom de variable comme un décalage d'un registre d'adresse (le pointeur de pile). Comme les autres ont dit, la pile est juste de ce PRINCIPE à l'égard de l'empilement (bon pour la récursivité, etc.). Ce n'est pas ce PRINCIPE à l'égard de l'accès. L'accès est totalement aléatoire.
  • Vous pouvez faire votre propre pile de la structure de données à l'aide d'un tableau, et juste le stockage de l'index de l'élément supérieur, en augmentant lorsque vous appuyez sur, le décrémenter lorsque vous pop. Si vous avez fait cela, si vous souhaitez toujours être en mesure d'accéder à tout élément individuel dans le tableau, à tout moment, sans pousser ou à éclater, comme vous pouvez toujours avec des tableaux. Environ la même chose se passe ici.
  • Bonne explication.
  • Merci à tous pour les commentaires. Vous m'avez aidé à obtenir une bien meilleure compréhension de ce bas niveau des choses. Vous rock!
  • Autre remarque: en C au moins, le compilateur n'est pas obligé de mettre des variables sur la pile à tous. L'optimiseur peut les garder dans les registres. Il peut également les placer sur la pile dans un ordre différent. Aussi, jetez un oeil à la "Suite" de la langue et de sa pile de la programmation orientée.
  • Un seul bloc de pile est un la plupart du temps non structuré zone de mémoire sous le seul contrôle du compilateur. Il n'y a pas de règles, que le compilateur doit obéir à l'intérieur d'une seule image. Il peut violer "dernier entré, premier sorti" et légalement dessin à main levée sur des octets comme il lui plaît. La structure de la pile a provient surtout de l'exigence que de nombreuses langues différentes doivent interagir et de s'appeler les uns les autres (à l'aide d'une convention d'appel).
  • Afin de fournir un point de vue historique: lorsque la pile a été d'abord inventé en effet, il était utilisé uniquement dans un dernier entré, premier sorti de la manière que vous avez poussé des données de la pile à enregistrer tout ce que vous avez quelque chose d'autre, a déclenché lorsque vous ont été faites. Alors, dans ces jours, comment avez-vous utiliser la pile pour stocker les variables locales? Généralement, vous n'avez pas! Les variables locales vécu dans la mémoire statique, et oui, cela signifiait que la plupart des procédures n'étaient pas ré-entrant. Et l'empilement des images ont été inventés, et il n'y a allégresse générale. 🙂
  • La norme ne mentionne même pas le mot "pile". Une zone contiguë de la pile comme celui en cours de discussion est certainement la façon la plus commune pour mettre en œuvre C du modèle de mémoire, mais ce n'est pas le seul. Il y a eu des compilateurs C que allouer de la mémoire pour les appels de fonction dans le tas, ou quelque chose comme ça, de sorte que la mémoire de commande de cadres pour les appels successifs est entièrement arbitraire.
  • Vous n'avez pas de pile variables, mais les images 🙂
  • Notez également que, pour votre exemple, certaines conventions d'appel n'a même pas de l'utilisation de la pile d'appel de stocker ou de transmettre ces valeurs, mais qui va simplement utiliser les registres.
  • Généralement, un pointeur de pile commence à la haute adresse de la pile et pousse à la baisse des adresses comme les choses sont poussés sur la pile. Les choses sont poussés sur la pile dans l'ordre de leur occurrence, de sorte que le haut de l'entrée sur le stack de " 1 "à l'entrée suivante de la pile(à une adresse inférieure) sera" 2 " et ainsi de suite.
  • Fondamentalement, le nom de pile/tas est regrettable. Ils ont peu de ressemblance avec la pile et le tas dans des structures de données de la terminologie, afin de l'appelant de la même est très déroutant.
  • eh bien, il a été rédigé de manière à dessiner un scénario spécifique. Comme je l'ai dit, j'avais lu à peu près tout ce que j'ai trouvé sur le sujet, mais ne pouvais pas trouver exactement ces bits manquants. Les gens a donné beaucoup de réponses ici, donc je pense que tout le monde devrait être heureux 🙂
  • IMO le meilleur moyen de comprendre correctement la pile et les conventions d'appel est d'écrire un peu de montage.
  • Mai , je sais qu'avez-vous voulu dire par "Mais là encore, cela signifie qu'il n'y a pas de pop-up de la pile jusqu'à ce que nous arrivons à a et b qui se passe." Je ne suis pas tout à fait le faire.
  • À partir d'un lien seule réponse: ce blog (cryptroix.com/2016/10/16/journey-to-the-stack) est assez bonne. Il utilise Python sous forme de pseudo-code pour la simple asm fonctions (comme vous pourriez trouver dans le compilateur C de sortie), et l'hypothèse d'un pile-args convention d'appel (moderne conventions d'appel de passer des arguments dans les registres)

InformationsquelleAutor Christoph | 2014-06-01