LIFO vs FIFO
LIFO signifie Last In, First Out. Comme dans, le dernier élément placé dans la pile est le premier élément retiré de la pile.
Ce que vous avez décrit avec votre analogie avec les plats (dans la première révision ), c'est une file d'attente ou FIFO, First In, First Out.
La principale différence entre les deux, c'est que le LIFO / pile pousse (insère) et saute (supprime) de la même extrémité, et une FIFO / file d'attente le fait des extrémités opposées.
// Both:
Push(a)
-> [a]
Push(b)
-> [a, b]
Push(c)
-> [a, b, c]
// Stack // Queue
Pop() Pop()
-> [a, b] -> [b, c]
Le pointeur de pile
Jetons un coup d'œil à ce qui se passe sous le capot de la pile. Voici un peu de mémoire, chaque case est une adresse:
...[ ][ ][ ][ ]... char* sp;
^- Stack Pointer (SP)
Et il y a un pointeur de pile pointant au bas de la pile actuellement vide (que la pile grandisse ou ne soit pas particulièrement pertinente ici, donc nous l'ignorerons, mais bien sûr dans le monde réel, cela détermine quelle opération ajoute , et qui soustrait du SP).
Alors poussons a, b, and c
encore. Graphique à gauche, opération "haut niveau" au milieu, pseudo-code C-ish à droite:
...[a][ ][ ][ ]... Push('a') *sp = 'a';
^- SP
...[a][ ][ ][ ]... ++sp;
^- SP
...[a][b][ ][ ]... Push('b') *sp = 'b';
^- SP
...[a][b][ ][ ]... ++sp;
^- SP
...[a][b][c][ ]... Push('c') *sp = 'c';
^- SP
...[a][b][c][ ]... ++sp;
^- SP
Comme vous pouvez le voir, chaque fois que nous le faisons push
, il insère l'argument à l'emplacement vers lequel pointe le pointeur de pile et ajuste le pointeur de pile pour pointer à l'emplacement suivant.
Voyons maintenant:
...[a][b][c][ ]... Pop() --sp;
^- SP
...[a][b][c][ ]... return *sp; // returns 'c'
^- SP
...[a][b][c][ ]... Pop() --sp;
^- SP
...[a][b][c][ ]... return *sp; // returns 'b'
^- SP
Pop
est l'opposé de push
, il ajuste le pointeur de pile pour pointer vers l'emplacement précédent et supprime l'élément qui était là (généralement pour le renvoyer à celui qui a appelé pop
).
Vous l'avez probablement remarqué b
et vous c
êtes toujours en mémoire. Je veux juste vous assurer que ce ne sont pas des fautes de frappe. Nous y reviendrons sous peu.
La vie sans pointeur de pile
Voyons ce qui se passe si nous n'avons pas de pointeur de pile. En commençant par pousser à nouveau:
...[ ][ ][ ][ ]...
...[ ][ ][ ][ ]... Push(a) ? = 'a';
Euh, hmm ... si nous n'avons pas de pointeur de pile, nous ne pouvons pas déplacer quelque chose à l'adresse vers laquelle il pointe. Peut-être pouvons-nous utiliser un pointeur qui pointe vers la base au lieu du haut.
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
...[a][ ][ ][ ]... Push(a) *bp = 'a';
^- bp
// No stack pointer, so no need to update it.
...[b][ ][ ][ ]... Push(b) *bp = 'b';
^- bp
Euh oh. Comme nous ne pouvons pas changer la valeur fixe de la base de la pile, nous avons simplement écrasé le a
en poussant b
au même emplacement.
Eh bien, pourquoi ne suivons-nous pas le nombre de fois où nous avons poussé. Et nous devrons également garder une trace des moments où nous sommes apparus.
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
int count = 0;
...[a][ ][ ][ ]... Push(a) bp[count] = 'a';
^- bp
...[a][ ][ ][ ]... ++count;
^- bp
...[a][b][ ][ ]... Push(a) bp[count] = 'b';
^- bp
...[a][b][ ][ ]... ++count;
^- bp
...[a][b][ ][ ]... Pop() --count;
^- bp
...[a][b][ ][ ]... return bp[count]; //returns b
^- bp
Eh bien, cela fonctionne, mais c'est en fait assez similaire à avant, sauf qu'il *pointer
est moins cher que pointer[offset]
(pas d'arithmétique supplémentaire), sans parler du fait qu'il est moins à taper. Cela me semble une perte.
Essayons encore. Au lieu d'utiliser le style de chaîne Pascal pour trouver la fin d'une collection basée sur un tableau (suivi du nombre d'éléments dans la collection), essayons le style de chaîne C (analyse du début à la fin):
...[ ][ ][ ][ ]... char* bp; // "base pointer"
^- bp bp = malloc(...);
...[ ][ ][ ][ ]... Push(a) char* top = bp;
^- bp, top
while(*top != 0) { ++top; }
...[ ][ ][ ][a]... *top = 'a';
^- bp ^- top
...[ ][ ][ ][ ]... Pop() char* top = bp;
^- bp, top
while(*top != 0) { ++top; }
...[ ][ ][ ][a]... --top;
^- bp ^- top return *top; // returns '('
Vous avez peut-être déjà deviné le problème ici. La mémoire non initialisée n'est pas garantie d'être 0. Ainsi, lorsque nous recherchons le haut à placer a
, nous finissons par ignorer un tas d'emplacements de mémoire inutilisés contenant des déchets aléatoires. De même, lorsque nous numérisons vers le haut, nous finissons par sauter bien au-delà de ce que a
nous venons de pousser jusqu'à ce que nous trouvions enfin un autre emplacement de mémoire qui se trouve être 0
, et reculons et renvoyons les déchets aléatoires juste avant cela.
C'est assez facile à corriger, il nous suffit d'ajouter des opérations Push
et Pop
de nous assurer que le haut de la pile est toujours mis à jour pour être marqué d'un 0
, et nous devons initialiser la pile avec un tel terminateur. Bien sûr, cela signifie également que nous ne pouvons pas avoir 0
(ou la valeur que nous choisissons comme terminateur) comme valeur réelle dans la pile.
En plus de cela, nous avons également changé les opérations O (1) en opérations O (n).
TL; DR
Le pointeur de pile garde une trace du haut de la pile, où se déroule toute l'action. Il existe des moyens de s'en débarrasser ( bp[count]
et top
sont essentiellement toujours le pointeur de pile), mais ils finissent tous les deux par être plus compliqués et plus lents que d'avoir simplement le pointeur de pile. Et ne pas savoir où se trouve le haut de la pile signifie que vous ne pouvez pas utiliser la pile.
Remarque: Le pointeur de pile pointant vers le "bas" de la pile d'exécution dans x86 peut être une idée fausse liée au fait que la pile d'exécution entière est à l'envers. En d'autres termes, la base de la pile est placée à une adresse mémoire élevée, et la pointe de la pile se développe en adresses mémoire inférieures. Le pointeur de pile ne pointe à l'extrémité de la pile où l'action se produit, il suffit que la pointe se trouve à une adresse mémoire inférieure à la base de la pile.