LIFO ve FIFO Karşılaştırması
LIFO, Son Giriş, İlk Çıkış anlamına gelir. Olduğu gibi, yığına yerleştirilen son öğe yığından alınan ilk öğedir.
Yemeklerin benzetmenizle ( ilk revizyonda ) tarif ettiğiniz şey, bir kuyruk veya FIFO, İlk Giren, İlk Çıkar.
İkisi arasındaki en büyük fark, LIFO / yığının aynı uçtan itmesi (takması) ve patlaması (çıkarması) ve bir FIFO / kuyruğunun zıt uçlardan yapmasıdır.
// Both:
Push(a)
-> [a]
Push(b)
-> [a, b]
Push(c)
-> [a, b, c]
// Stack // Queue
Pop() Pop()
-> [a, b] -> [b, c]
Yığın işaretçisi
Yığının kaputunun altında neler olduğuna bir bakalım. İşte biraz bellek, her kutu bir adres:
...[ ][ ][ ][ ]... char* sp;
^- Stack Pointer (SP)
Ve şu anda boş yığının altına işaret eden bir yığın işaretçisi var (yığının büyüyüp büyüyüp büyümediği burada özellikle alakalı değil, bu yüzden bunu görmezden geleceğiz, ancak elbette gerçek dünyada hangi işlemin eklendiğini belirleyecek ve SP'den çıkarılır).
Hadi a, b, and c
tekrar itelim. Soldaki grafikler, ortadaki "yüksek seviye" işlemi, sağdaki C-ish sözde kodu:
...[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
Gördüğünüz gibi, her seferinde push
, bağımsız değişkeni yığın işaretçisinin işaret ettiği konuma ekler ve yığın işaretçisini sonraki konumu gösterecek şekilde ayarlar.
Şimdi başlayalım:
...[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
tersi ise push
, yığın işaretçisini önceki konumu gösterecek şekilde ayarlar ve orada bulunan öğeyi kaldırır (genellikle çağıran kişiye geri döndürür pop
).
Muhtemelen bunu fark ettiniz b
ve c
hala hafızadasınız. Ben sadece bunların yazım hatası olmadığından emin olmak istiyorum. Kısa süre içinde buna geri döneceğiz.
Yığın işaretçisi olmayan hayat
Bir yığın işaretçimiz yoksa ne olacağını görelim. Tekrar iterek başlayarak:
...[ ][ ][ ][ ]...
...[ ][ ][ ][ ]... Push(a) ? = 'a';
Ee, hmm ... yığın işaretçimiz yoksa, işaret ettiği adrese bir şey taşıyamayız. Belki üst yerine tabana işaret eden bir işaretçi kullanabiliriz.
...[ ][ ][ ][ ]... 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
Ah oh. Yığının tabanının sabit değerini değiştiremediğimizden , aynı konuma a
iterek üzerine yazdık b
.
Neden kaç kez ittiğimizi takip etmiyoruz. Ayrıca, attığımız zamanları da takip etmemiz gerekecek.
...[ ][ ][ ][ ]... 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
İşe yarıyor, ama aslında öncekine oldukça benziyor, ancak *pointer
yazımdan daha pointer[offset]
az bahsetmiyorum bile ( daha fazla aritmetik yok) daha ucuz . Bu benim için bir kayıp gibi görünüyor.
Tekrar deneyelim. Dizi tabanlı bir koleksiyonun sonunu bulmak için Pascal dize stilini kullanmak yerine (koleksiyonda kaç öğenin bulunduğunu izleme), C dize stilini deneyelim (baştan sona tarayın):
...[ ][ ][ ][ ]... 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 '('
Sorunu burada önceden tahmin etmiş olabilirsiniz. Başlatılmamış belleğin 0 olması garanti edilmez. Bu nedenle, en üste yerleştirmek için baktığımızda a
, içinde rastgele çöp bulunan bir grup kullanılmayan bellek yerinin üzerinden atlıyoruz. Benzer şekilde, en üste tarama a
yaptığımızda, nihayet yeni bir bellek konumu bulana kadar ittik ve ötesine 0
geçip rastgele çöpü ondan hemen önce geri döndürüyoruz.
Bu düzeltmek için yeterince kolay, sadece yığının üst kısmının her zaman bir ile işaretlenecek şekilde güncellendiğinden emin olmak Push
ve işlemleri eklememiz ve yığını böyle bir sonlandırıcıyla başlatmamız gerekiyor. Tabii ki bu aynı zamanda yığında gerçek bir değer olarak (veya sonlandırıcı olarak seçtiğimiz herhangi bir değere) sahip olamayacağımız anlamına da gelir .Pop
0
0
Bunun da ötesinde, O (1) operasyonlarını neyin O (n) operasyonlarına değiştirdik.
TL; DR
Yığın işaretçisi, tüm eylemin gerçekleştiği yığının üstünü izler . Ondan kurtulmanın bir yolu vardır ( bp[count]
ve top
aslında hala yığın işaretçisidir), ancak ikisi de yığın işaretçisine sahip olmaktan daha karmaşık ve yavaş olur. Ve yığının üst kısmının nerede olduğunu bilmemek, yığını kullanamayacağınız anlamına gelir.
Not: x86'da çalışma zamanı yığınının "en altını" gösteren yığın işaretçisi, tüm çalışma zamanı yığınının baş aşağı olmasıyla ilgili bir yanlış anlama olabilir. Başka bir deyişle, yığının tabanı yüksek bir bellek adresine yerleştirilir ve yığının ucu daha düşük bellek adreslerine doğru büyür. Yığın işaretçisi yapar tüm eylem ucu yığının tabanından daha düşük bir bellek adresi olarak, sadece bu meydana istifin ucuna noktası.