İşlev çağrısı anlambilimini temsil etmek için bir yığın kullanmanın alternatifleri nelerdir?


19

Hepimiz biliyoruz ve işlev çağrıları genellikle yığın kullanılarak yapılıyor; çerçeveler, dönüş adresleri, parametreler, bütün lot var.

Bununla birlikte, yığın bir uygulama detayıdır: çağrı kuralları farklı şeyler yapabilir (yani x86 hızlı arama kullanır (bazı) kayıtlar, MIPS ve takipçiler kayıt pencereleri kullanır, vb.) Ve optimizasyonlar başka şeyler de yapabilir (satır içi, çerçeve işaretçisi ihmali, kuyruk çağrı optimizasyonu ..).

Elbette, birçok makinede uygun yığın talimatının varlığı (JVM ve CLR gibi VM'ler, aynı zamanda PUSH / POP'ları ile x86 gibi gerçek makineler), işlev çağrıları için kullanılmasını kolaylaştırır, ancak bazı durumlarda mümkündür bir çağrı yığını gerekli olmayan bir şekilde programlamak için (Ben burada Devam Geçiş Tarzı veya bir mesaj geçiş sistemindeki Aktörler düşünüyorum)

Merak etmeye başladım: işlev çağrısı semantiği yığın olmadan veya daha iyisi, farklı bir veri yapısı (bir kuyruk, ya da ilişkilendirilebilir bir harita) kullanarak uygulamak mümkün mü?
Tabii ki, bir yığının çok uygun (her yerde olması için bir neden var) ama son zamanlarda beni merak ettiren bir uygulamaya çarptı ..

Herhangi birinizin herhangi bir dilde / makinede / sanal makinede yapılıp yapılmadığını ve eğer öyleyse çarpıcı farklılıkların ve eksikliklerin neler olduğunu biliyor musunuz?

EDIT: Bağırsak duygularım farklı alt hesaplama yaklaşımlarının farklı veri yapılarını kullanabileceğidir. Örneğin, lambda hesabı yığın tabanlı değildir (işlev uygulaması fikri indirimlerle yakalanır) ama gerçek bir dile / makineye / örneğe bakıyordum. Bu yüzden soruyorum ...


Clean , bir grafik ve bir grafik yeniden yazma makinesi kullanır, bu da üç yığınlı bir makine kullanılarak uygulanır, ancak genellikle normalden farklı içeriklidir.

Sanal makineler için bağlantılı bir liste kullanılabilir. Listenin her düğümü bir çerçevedir. Donanım yığını VM tarafından kullanıldığından, çerçevelerin yığın üzerinde ek yük olmadan var olmasına izin verir realloc().
shawnhcorey

Yanıtlar:


19

Dile bağlı olarak, bir çağrı yığını kullanmak gerekli olmayabilir. Çağrı yığınları yalnızca özyineleme veya karşılıklı özyinelemeye izin veren dillerde gereklidir. Dil özyinelemeye izin vermiyorsa, herhangi bir prosedürün yalnızca bir çağrılması her an etkin olabilir ve bu prosedür için yerel değişkenler statik olarak tahsis edilebilir. Bu tür diller, kesinti işleme için bağlam değişiklikleri için hazırlık yapmak zorundadır, ancak bu yine de bir yığın gerektirmez.

Çağrı yığını gerektirmeyen dil örnekleri için FORTRAN IV'e (ve önceki sürümlerine) ve COBOL'un ilk sürümlerine bakın.

Çağrı yığınları için doğrudan donanım desteği sağlamayan oldukça başarılı bir erken süper bilgisayar örneği için Kontrol Verileri 6600'e (ve daha önceki Kontrol Verileri makinelerine) bakın. Çağrı yığınlarını desteklemeyen çok başarılı bir erken mini bilgisayar örneği için PDP-8'e bakın.

Bildiğim kadarıyla Burroughs B5000 yığın makineleri, donanım çağrı yığınlarına sahip ilk makinelerdi. B5000 makineleri sıfırdan tekrarlama gerektiren ALGOL'u çalıştıracak şekilde tasarlanmıştır. Ayrıca, yetenek mimarileri için zemin hazırlayan ilk tanımlayıcı tabanlı mimarilerden birine sahiptiler.

Bildiğim kadarıyla, MIT'deki hacker topluluğu bir tanesini teslim aldığında ve PUSHJ (Push Return Address and Jump) işleminin olduğunu keşfettiğinde, PDP-6 (DEC-10'a dönüşen) popüler çağrı yığını donanımıydı. ondalık yazdırma yordamının 50 komuttan 10'a düşürülmesine izin verdi.

Özyinelemeye izin veren bir dilde en temel işlev çağrı semantiği, bir yığınla güzel eşleşen yetenekler gerektirir. İhtiyacınız olan tek şey buysa, temel bir yığın iyi ve basit bir eşleşmedir. Bundan daha fazlasına ihtiyacınız varsa, veri yapınızın daha fazlasını yapması gerekir.

Karşılaştığımdan daha fazlasına ihtiyaç duymanın en iyi örneği, "devam", ortada bir hesaplamayı askıya alma, donmuş bir devlet balonu olarak kaydetme ve daha sonra, muhtemelen birçok kez tekrar ateşleme yeteneğidir. Sürdürmeler, diğer şeylerin yanı sıra, hata çıkışlarını uygulamanın bir yolu olarak LISP'nin Şema lehçesinde popüler hale geldi. Devamlar, geçerli yürütme ortamını anlık olarak alma ve daha sonra yeniden oluşturma yeteneğini gerektirir ve bir yığın bunun için biraz rahatsız edici olur.

Abelson & Sussman'ın "Bilgisayar Programlarının Yapısı ve Yorumlanması" , devamlarla ilgili bazı ayrıntılara girmektedir.


2
Bu harika bir tarihsel içgörü, teşekkürler! Sorumu sorduğumda, özellikle devam eden stil (CPS) başta olmak üzere, gerçekten de devam eden aklımlar vardı. Bu durumda, bir yığın sadece elverişli değil, aynı zamanda muhtemelen gerekli değildir: nereye geri döneceğinizi hatırlamanıza gerek yoktur, yürütmeye nerede devam edeceğinize dair bir nokta sağlarsınız. Diğer yığınsız yaklaşımların yaygın olduğu yerlerde merak ettim ve farkında olmadığım bazı iyi şeyler verdiniz.
Lorenzo Dematté

Biraz ilgili: “dil özyinelemeye izin vermiyorsa” doğru bir şekilde işaret ettiniz. Özyinelemeli dil, özellikle kuyruk özyinelemeli olmayan fonksiyonlar ne olacak? Bir yığın "tasarım gereği" gerekiyor mu?
Lorenzo Dematté

"Çağrı yığınları yalnızca özyineleme veya karşılıklı özyinelemeye izin veren dillerde gereklidir" - hayır. Bir işlev birden fazla yerden çağrılabiliyorsa (örn. Her ikisi de foove barçağırabilir baz), o zaman işlevin neye geri döneceğini bilmesi gerekir. Bu "kime geri dönecek" bilgisini iç içe yerleştirirseniz, bir yığınla sonuçlanırsınız. Ne dediğiniz veya CPU'nun donanımı veya yazılımda taklit ettiğiniz bir şey tarafından destekleniyorsa (veya statik olarak tahsis edilmiş girişlerin bağlantılı bir listesi olsa bile), yine de bir yığın.
Brendan

@Brendan mutlaka değil (en azından sorumun bütün amacı bu). "Nereye dönülmeli" ya da daha iyisi "nereye gitmeli" bir yığın mı, yani bir LIFO yapısı mı olmalı? Bir ağaç, bir harita, bir kuyruk veya başka bir şey olabilir mi?
Lorenzo Dematté

Örneğin, bağırsak hislerim CPS'nin sadece bir ağaca ihtiyacı olduğu, ancak emin değilim, nereye bakacağımı da bilmiyorum. Bu yüzden soruyorum ..
Lorenzo Dematté

6

Bir tür yığın kullanmadan işlev çağrısı anlambilimi uygulamak mümkün değildir. Sadece kelime oyunları oynamak mümkündür (örneğin "FILO return buffer" gibi farklı bir isim kullanın).

İşlev çağrısı anlambilimi uygulamayan bir şey kullanmak (örneğin, devam geçiş tarzı, aktörler) ve daha sonra bunun üzerine işlev çağrısı anlambilimi oluşturmak mümkündür; ancak bu, işlev döndüğünde denetimin geçildiği yeri izlemek için bir tür veri yapısı eklemek anlamına gelir ve bu veri yapısı bir tür yığın (veya farklı bir ad / açıklamaya sahip bir yığın) olacaktır.

Birbirinizi çağırabilecek birçok fonksiyonunuz olduğunu düşünün. Çalışma zamanında, her bir işlev, işlevden çıkıldığında nereye dönüleceğini bilmelidir. firstAramalar varsa secondo zaman var:

second returns to somewhere in first

Ardından, secondaramalarınız thirdvarsa:

third returns to somewhere in second
second returns to somewhere in first

Ardından, thirdaramalarınız fourthvarsa:

fourth returns to somewhere in third
third returns to somewhere in second
second returns to somewhere in first

Her fonksiyon çağrıldıkça, daha fazla "nereye dönüleceği" bilgisi bir yerde saklanmalıdır.

Bir işlev geri dönerse, onun "nereye döneceği" bilgileri kullanılır ve artık gerekli değildir. Örneğin, bir fourthyere geri dönerse third, bilgi "nereye geri dönülür" miktarı şu şekilde olur:

third returns to somewhere in second
second returns to somewhere in first

Temelde; "işlev çağrısı anlambilimi" şu anlama gelir:

  • "nereye döneceğiniz" bilgisine sahip olmalısınız
  • fonksiyonlar çağrıldıkça bilgi miktarı artar ve fonksiyonlar döndüğünde azalır
  • depolanan "nereye dönülecek" bilgisinin ilk parçası atılan "nereye dönülecek" bilgisinin son parçası olacaktır

Bu bir FILO / LIFO tamponunu veya istifini tanımlar.

Bir ağaç türü kullanmaya çalışırsanız, ağaçtaki her düğümün asla birden fazla çocuğu olmaz. Not: birden fazla çocuğa sahip bir düğüm, ancak bir işlev aynı anda 2 veya daha fazla işlevi çağırdığında gerçekleşebilir , bu da bir çeşit eşzamanlılık gerektirir (örneğin, iş parçacıkları, çatal (), vb.) Ve "işlev çağrısı semantiği" olmaz. Ağaçtaki her düğümün asla birden fazla çocuğu olmazsa; o zaman bu "ağaç" sadece bir FILO / LIFO tamponu veya bir yığın olarak kullanılır; ve sadece FILO / LIFO tamponu veya bir yığın olarak kullanıldığından, "ağaç" ın bir yığın olduğunu iddia etmek doğrudur (ve tek fark kelime oyunları ve / veya uygulama detaylarıdır).

Aynı şey, "işlev çağrısı anlambilimi" uygulamak için akla yatkın olarak kullanılabilecek diğer tüm veri yapıları için de geçerlidir - yığın olarak kullanılacaktır (ve tek fark kelime oyunları ve / veya uygulama detaylarıdır); "işlev çağrısı anlambilimi" ni kırmazsa. Not: Mümkünse diğer veri yapılarına örnekler verebilirim, ancak biraz akla yatkın başka bir yapı düşünemiyorum.

Elbette bir yığının nasıl uygulandığı bir uygulama detayıdır. Bu bir bellek alanı olabilir ("mevcut yığın üstünü" takip ettiğiniz yerde), bir çeşit bağlantılı liste olabilir ("listedeki geçerli girişi" izlediğiniz yerde) veya bazılarına uygulanabilir Diğer yol. Donanımın yerleşik desteği olup olmadığı da önemli değildir.

Not: Herhangi bir prosedürün sadece bir çağrılması herhangi bir anda etkin olabilirse; "nereye geri dönüleceği" bilgilerine statik olarak yer ayırabilirsiniz. Bu hala bir yığıntır (örneğin FILO / LIFO yolunda kullanılan statik olarak tahsis edilmiş girdilerin bağlantılı bir listesi).

Ayrıca, "işlev çağrısı anlambilimi" ni takip etmeyen bazı şeyler olduğunu unutmayın. Bunlar arasında "potansiyel olarak çok farklı semantikler" (örn. Devam etme, aktör modeli); aynı zamanda eşzamanlılık (iplikler, lifler, ne olursa olsun), setjmp/ longjmp, istisna işleme vb. gibi "işlev çağrısı anlambilimi" için ortak uzantılar içerir


Tanım olarak, bir yığın bir LIFO koleksiyonudur: Son giren ilk çıkar. Sıra, bir FIFO koleksiyonudur.
John R. Strohm

Öyleyse bir yığın kabul edilebilir tek veri yapısı mıdır? Öyleyse neden?
Lorenzo Dematté

@ JohnR.Strohm: Sabit :-)
Brendan

1
Özyinelemesiz (doğrudan veya mutal) diller için, her yönteme, o yöntemin en son çağrıldığı yeri tanımlayacak bir değişkeni statik olarak ayırmak mümkündür. Bağlayıcı bu tür şeylerin farkındaysa, bu tür değişkenleri, statik olarak mümkün olan her yürütme yolu gerçekten alınmış olsaydı, yığının ne yapacağından daha kötü olmayacak şekilde tahsis edebilir .
supercat

4

Oyuncak birleşik dili XY , yürütme için bir çağrı kuyruğu ve bir veri yığını kullanır.

Her hesaplama adımı basitçe yürütülecek bir sonraki sözcüğü ayıklamayı içerir ve yerleşikler söz konusu olduğunda, veri işlevini ve çağrı kuyruğunu bağımsız değişkenler olarak veya kullanıcı tanımlarıyla besleyerek sözcükleri sıranın önüne iter.

Üst öğeyi ikiye katlayacak bir fonksiyonumuz varsa:

; double dup + ;
// defines 'double' to be composed of 'dup' followed by '+'
// dup duplicates the top element of the data stack
// + pops the top two elements and push their sum

Sonra oluşturma işlevleri +ve dupaşağıdaki yığın / kuyruk türü imzalar var:

// X is arbitraty stack, Y is arbitrary queue, ^ is concatenation
+      [X^a^b Y] -> [X^(a + b) Y]
dup    [X^a Y] -> [X^a^a Y]

Ve paradoksal olarak, şöyle doublegörünecek:

double [X Y] -> [X dup^+^Y]

Yani bir anlamda, XY istifsizdir.


Vay canına, teşekkürler! Buna bakacağım ... gerçekten işlev çağrıları için geçerli olduğundan emin değilim ama yine de bir göz atmaya değer
Lorenzo Dematté

1
@ Karl Damgaard Asmussen "diziyi oluşturan kelimeleri sıraya itiyor" "öne itiyor" Bu bir yığın değil mi?

@ guesttttttt222222222 gerçekten değil. Bir calltack dönüş işaretçileri saklar ve işlev döndüğünde calltack açılır. Yürütme kuyruğu yalnızca işlevlere işaretçiler depolar ve bir sonraki işlevi yürütürken tanımına genişletilir ve kuyruğun önüne doğru itilir. XY'de yürütme kuyruğu aslında bir deque, çünkü yürütme kuyruğunun arkasında da çalışan işlemler var.
Karl Damgaard Asmussen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.