2 puan yazan GN⁺ 2024-04-04 | 1 yorum | WhatsApp'ta paylaş
  • İlk bilgisayarlar, stack ve heap olmadan da fonksiyon çağrılarını gerçekleştirmek zorundaydı; derleyiciler çağrı durumunu parametreler, dönüş adresi ve yerel değişkenlere karşılık gelen gizli global değişkenlerle yönetiyordu
  • Çağıran taraf argümanları kaydedip dönüş adresi değişkenine geri dönülecek konumu yazdıktan sonra fonksiyonun başlangıç noktasına atlar, fonksiyon da hesaplamayı bitirdikten sonra kaydedilmiş adrese yeniden atlar
  • Mantıksal yerel değişkenler de aslında global depolama alanını kullandığı için, dışarıdan fonksiyon gibi görünse de iç işleyiş sabit bellek ve gotoya daha yakındı
  • Bazı ABI’ler ve işlemciler argüman aktarımı ile dönüş adresi işlemeyi register’lar veya branch with link ile optimize etse de temel kısıt değişmeden kaldı
  • Aynı fonksiyonun dönüş adresi yeni bir çağrıyla üzerine yazıldığı için özyinelemeli çağrı mümkün değildi; dönemin dilleri buna özyinelemeyi yasaklayarak ya da yalnızca açıkça izin verildiğinde destekleyerek karşılık verdi

Stack olmadan fonksiyon çağrısı nasıl kurulur

  • Erken dönem bilgisayar ortamlarında bugün doğal kabul edilen stack veya heap yoktu
  • Heap olmadan dinamik bellek ayırma, sabit boyutlu tamponlarla ikame edilebiliyordu
    • Değişken boyutlu veriler işlenirken bile önceden yeterince büyük sabit tamponlar ayrılırdı
    • İstenen veri tampon kapasitesini aşarsa program ölümcül bir hatayla sonlandırılırdı
    • Daha kullanıcı dostu uygulamalar, en yüksek kapasitenin derleme sırasında ayarlanmasına izin verirdi
    • Daha gelişmiş uygulamalar, sabit tamponun üstüne özel bir ayırıcı koyup bunu allocate ve free benzeri şekilde kullanabilirdi

Gizli global değişken tabanlı çağrı kuralı

  • Derleyiciler stack olmadan fonksiyon çağrısı yapabilmek için her fonksiyon için birkaç gizli global değişken tanımlardı
    • Her giriş parametresi için bir global değişken
    • Fonksiyonun dönüş adresini tutan bir global değişken
    • Yerel değişkenlere karşılık gelen global değişkenler
  • Çağrı kodu şu sırayla çalışırdı
    • Parametre değerleri ilgili gizli global değişkenlere yazılırdı
    • Geri dönülecek konum, fonksiyonun dönüş adresi değişkenine kaydedilirdi
    • Fonksiyonun başlangıç konumuna goto ile atlanırdı
  • Fonksiyon, hem parametreleri hem de yerel değişkenleri bu gizli global değişkenlerden okur ve onlara yazar
  • İş bitince dönüş değeri dönüş değeri register’ına konur ve fonksiyonun dönüş adresi değişkeninde saklanan adrese atlanırdı

C benzeri kodun goto tabanlı koda dönüşmesine örnek

  • Örnek fonksiyon add_two_values(int a, int b), stack olmadan şu depolama alanlarına dönüştürülebilirdi
    • a2v_a ve a2v_b, argümanları saklayan global değişkenlerdi
    • a2v_c, yerel c değişkenine karşılık gelen global değişkendi
    • a2v_retaddr, geri dönüş adresini saklayan global değişkendi
  • Çağıran sample(), 31415 ve 2718 değerlerini sırasıyla argüman global değişkenlerine yazardı
  • Ardından a2v_retaddr içine resume konumunu koyup add_two_values fonksiyonuna atlardı
  • add_two_values, hesaplama sonucunu return_value_register içine yazdıktan sonra a2v_retaddr üzerinden geri dönerdi
  • resume konumuna geri dönen çağıran taraf, dönüş değeri register’ındaki değeri sample_x içine kaydederdi

Register’lar ve branch with link ile optimizasyon

  • Aynı yapı, ABI düzeyinde register üzerinden aktarım ile daha hızlı hale getirilebilirdi
  • Birçok işlemci özel bir link register ve branch with link komutu sunuyordu
    • branch with link, dallanma komutundan sonraki komutun adresini otomatik olarak link register’a kaydeder
    • Çağıran taraf ilk iki argümanı argument_register_1 ve argument_register_2 içine koyabilirdi
    • Çağrılan fonksiyon bu register değerlerini kendi gizli global değişkenlerine taşıyıp kullanabilirdi
  • Dönüş adresi de link_register içinden fonksiyonun dönüş adresi değişkenine kaydedilebilirdi
  • Bu optimizasyon, stack olmadan da çağrı ve dönüş yapılabilmesi yönündeki temel yapıyı korurdu

Özyineleme neden engelleniyordu

  • Bu çağrı yönteminin temel kısıtı, özyinelemeli çağrının mümkün olmamasıydı
  • Özyinelemeli çağrı gerçekleştiğinde aynı fonksiyonun dönüş adresi değişkeni yeni çağrının dönüş adresiyle üzerine yazılırdı
  • Dıştaki çağrı tamamlandığında aslında dönülmesi gereken özgün konum kaybolduğu için yanlış yere atlanırdı
  • Dönemin programlama dilleri bu sorunu, özyinelemeyi desteklemeyerek aşardı
  • FORTRAN başlangıçta alt programları bile desteklemiyordu; alt programlar 1958’de eklendi
  • FORTRAN’da özyineleme desteğinin standart hale gelmesi 1991’i buldu ve o zaman bile alt programın RECURSIVE olarak açıkça belirtilmesi gerekiyordu

Kendini değiştiren kod ve erken işlemcilerde alt program komutları

  • Bazı derleyiciler daha da zekice davranıp kendini değiştiren kod kullanıyordu
    • Fonksiyon sonundaki atlama komutunun içindeki adres alanı fiilen dönüş adresi değişkeni görevini görüyordu
  • Bu yaklaşım yalnızca basit bir numara değil, pratik bir gereklilik de olabiliyordu
    • Bazı işlemciler dolaylı atlamayı desteklemiyor olabilirdi
  • Alt programların pratik değeri anlaşıldıktan sonra birçok işlemci özel çağrı komutları ekledi
    • Dönüş adresi alt programın ilk sözcüğüne yazılırdı
    • Asıl yürütme ikinci sözcükten başlardı
    • Geri dönüş sırasında alt programın başlangıç etiketi üzerinden dolaylı atlama yapılırdı
  • Örnek assembly’de bsr add_two_values, dönüş adresini add_two_values’ın ilk sözcüğüne yazar ve kurbanlık nop sonrasındaki gerçek komutlardan yürütmeye başlar

1 yorum

 
GN⁺ 2024-04-04
Hacker News yorumları
  • Bu konuda The Art of Computer Programming gerçekten çok iyiydi
    Dışarıdan bakınca eski görünebilir ama heap ya da stack öncesi dönemde dinamik olarak değişen dizilerle ve veri yapılarıyla uğraşan çok sayıda algoritma var
    Kitap, çöp toplamaya ve Lisp listesi uygulamasına kadar adım adım ilerliyor; Knuth’tan bekleyeceğiniz ansiklopedik bilgi aynen mevcut
    Özellikle sevdiğim örnek, iki dizinin tek bir alanı dinamik olarak paylaşma biçimi. Bir dizi location#0 konumundan ileri doğru, ikinci dizi ise location#End konumundan geriye doğru büyürse, statik olarak ayrılmış alanı verimli biçimde paylaşmış oluyorsunuz
    Bunu keyfî sayıda diziye genişletmek de mümkün, ama o noktada sadece Malloc ve Realloc kullanmak daha iyi; tekniğin kendisi de malloc benzeri bir rutine oldukça yakın

    • 8 bit bilgisayarlardaki bazı kelime işlemciler böyle çalışıyordu. Belge, kullanılabilir RAM’in tamamını kaplıyor; imlecin önündeki metin RAM’in başında, imlecin arkasındaki metin ise RAM’in sonunda duruyordu
      Ekleme ve yapıştırma işlemlerinde veriyi kaydırmak gerekmiyordu, ama gezinirken gerekiyordu. Yine de iyi çalışıyordu
    • Çoğu komut kümesi mimarisinde ve ABI’de stack yüksek adreslerden aşağı doğru büyüdüğü için, tek iş parçacıklı küçük bellekli sistemlerde bu teknik heap ile stack arasındaki belleği esnek biçimde paylaştırmayı sağlıyordu
    • Eski MacOS’ta uygulama başına kaynak ayırma tam da bu şekilde açıklanır. Her uygulamanın bir minimum RAM gereksinimi ve tercih edilen RAM gereksinimi vardı; çalıştırıldığında tercih edilen boyutta bir slot kaplıyordu
      O kadar yer yoksa tercih edilenden daha küçük ayrılıyor, minimumu bile alamazsa çalıştırma başarısız oluyordu
      Hatırladığım kadarıyla sistem, o fiziksel RAM parçasının alt tarafına heap’i ve kütüphaneleri, üst tarafına da stack’i yerleştiriyordu
      System 8 civarında bir sanallaştırma katmanı eklenince bu yaklaşıma daha az ihtiyaç kaldı; MacOS X döneminde ise diğer sistemler gibi sayfalı bellek kullanıldığı için bu tür numaralara artık gerek kalmadı
      Yine de Art of Computer Programming’deki bu tür “tuhaf tek numara”nın, aynı anda çalışan birden çok uygulama için RAM ayırma yöntemi olduğu dönemi düşünmek eğlenceli
    • İlginç bir bilgi: Itanium’da toplam iki stack vardı; biri elle push/pop için, diğeri de register file üzerinde dönen ayrı bir stack’ti
      Biri yukarı, diğeri aşağı doğru büyüyordu. Büyüleyici bir yapıydı ama vaat ettiği performansı sonunda veremedi
    • SQLite’ın disk biçimi de tablo B-tree yaprak düğümü sayfası içeriğini depolarken benzer bir dizi tekniği kullanır
      Sabit boyutlu bir sayfada offset dizisi ileri doğru büyür, değişken uzunluklu satır değerleri dizisi ise sondan geriye doğru büyür. Satır silindiğinde arka taraftaki dizide boşluklar oluşabildiğini anlıyorum
      Dokümantasyon B-tree yapısının kendisi için TAOCP’ye atıf yaptığından, doğrudan ilham kaynağı olmuş olsa şaşırmam
  • ALGOL’e özyinelemeli fonksiyonlar eklemek epey tartışmalıydı ve hâlâ anlatması keyifli bir hikâye: https://vanemden.wordpress.com/2014/06/18/how-recursion-got-...

  • SUBLEQ makinesi için bir Forth yorumlayıcısı (https://github.com/howerj/subleq) ve bit-serial makine için bir yorumlayıcı (https://github.com/howerj/bit-serial) yazmıştım; ikisinde de Forth için gereken fonksiyon çağrı yığını yoktu
    SUBLEQ dolaylı yükleme/saklamaya da izin vermediğinden, azıcık karmaşık bir şey yapmak için kendi kendini değiştiren kod gerekiyor
    İki makinede de bu işlevi yerine getirebilecek bir sanal makine oluşturup içine işbirlikçi multithreading de ekleyerek yaklaştım
    Heap gerekiyorsa Forth ile yazıyorsunuz; kayan nokta word kümesini de Forth ile yazıyorsunuz. Birçok MCU’da hâlâ kayan nokta komutu yok ve bu, onu uygulayan yazılım fonksiyon çağrılarıyla halledilebiliyor
    Bahsedilmese de diğer derleyicilerin de benzer bir yol izlediğini düşünüyorum. Bazı BASIC yorumlayıcıları da bir VM uygulayıp onu hedef aldı; P-Code da buna benzer

    • TI-99/4A’da CPU’nun doğrudan erişebildiği ana RAM yalnızca 256 bayt, yani 128 word idi
      Temel sistem belleğinin çoğu video RAM’iydi ve video çipi yazmaçlarına poke/peek yapmayı gerektiren epey zahmetli bir prosedürle erişilmesi gerekiyordu
      Video çipi, otomatik artan bir geçerli bellek işaretçisini tutuyordu; ardışık okuma veya yazmada işaretçi 1 artıyordu. Ancak sistem belleğinin çoğuna yalnızca bu yolla erişilebilmesi, büyük programlar yazmayı başlı başına zorlaştırıyordu
      Bu yüzden TI, bu video RAM erişimini daha doğal hâle getiren GPL adlı soyut bir makine oluşturdu. Ancak TMS9900 üzerinde yorumlanarak çalıştığı için native koddan yavaştı; ayrıca CPU video çipi RAM’ine yalnızca çipin ekran taraması yapmadığı yatay/dikey geri dönüş aralıkları gibi anlarda erişebildiğinden daha da yavaştı
      BASIC kodu ve değişkenleri de tamamen bu video bellekteydi; dolayısıyla TI-99/4A’nın BASIC yorumlayıcısının neyle yazıldığı da ortada. Hiç hızlı değildi
      İlginç olan nokta, TMS9900’de gerçek genel amaçlı yazmaçların olmamasıydı. WR0~WR15 çalışma alanı yazmaçları belleğin bir yerindeydi ve WP çalışma alanı işaretçisi yazmacı bunları gösteriyordu
      CPU’nun fiziksel yazmaçları yalnızca PC, WP ve durum yazmacı olmak üzere üç taneydi. Sonuçta çok ilkel bir register windowing yapılabiliyordu; BLWP komutuyla dallanınca bellekte başka bir konumdaki yeni “yazmaç” kümesi etkinleşiyor ve dönüş adresi yeni çalışma alanına kaydediliyordu
      Son zamanlarda TI-99/4A’dan sık söz etmemin nedeni, kişisel proje olarak bu model için bir assembler geliştiriyor olmam
    • Forth ve Subleq’i öğrenip kurcalarken bu çalışmaları görmüştüm. Yaklaşımlarını okumak hoşuma gitti; kitabı almak istedim ama Amazon’da mümkün olmadığını söylüyor. Yeni baskı olup olmayacağını merak ediyorum
    • subleq’ten bahsedecektim; orada tek bir “Hello world” yazmak bile gerçekten zor
  • Bazı işlemcilerin dönüş adresini alt yordamın ilk komutundan hemen önceki word’e kaydettiği sözü doğru; PDP-8 böyle yapıyordu
    PDP-8’in evrimi, özyineleme için donanım desteğine giden bir yolculuk olarak da görülebilir
    Başta JMS komutu dönüş adresini fonksiyonun ilk word’üne gömüyordu. Çağıranın argümanları JMS komutunun arkasına koyduğu, çağrılanın da dönüş komutuna göre ofsetlerle argümanları okuyup her seferinde artırarak dönüş adresinin tekrar kod konumunu göstermesini sağladığı durumlar da yaygındı
    Daha sonra otomatik artan konumlardan birini kullanarak basit bir stack yapmak oldukça yaygınlaştı. PDP-8’de işaretçi olarak her kullanıldığında artan 8 bellek konumu vardı; fonksiyon prologue/epilogue kodu bu stack’i doğrudan yöneterek tam özyinelemeyi mümkün kılıyordu
    Daha sonra Harris 6120 gibi mikroişlemci uygulamalarına donanım stack’i eklenerek performans iyileştirildi

    • 1956 tarihli Librascope LGP-30’da R komutu, yani dönüş adresi saklama komutu vardı
      Bu komut, zaten artırılmış PC+1 değerini hedef konumdaki komutun adres kısmına kaydediyordu; geleneksel olarak bu hedef, alt yordamın başlangıcından hemen önceki koşulsuz dallanma komutuydu
      R komutunun ardından ilgili alt yordama giden U koşulsuz dallanma komutu konurdu
      Alt yordam, kendi önündeki adrese dallanarak dönerdi; orada da çağrı noktasının hemen sonrasına dönen koşulsuz dallanma bulunurdu
      Daha gelişmiş bir çağrı konvansiyonu kullanılmadıkça özyineleme mümkün değildi. Ayrıca assembly dilindeki tüm komut kodları tek harfti
    • IBM 1800, IBM 1130 ve o dönemin birçok makinesi de böyleydi. Xerox Sigma ailesi gibi yeterince yazmacı olan makineler bu alışkanlıktan kaçınabiliyordu
  • AVR-8 için yazılan programlarda C çağrı konvansiyonunu kullanmak bazen delilik gibi geliyor
    Assembly kullanınca iç döngü değişkenlerini büyük yazmaç dosyasında sürekli tutabiliyor ya da yazıda anlatılan yöntemleri kullanabiliyorsunuz
    Böyle uygulamalarda fonksiyonları “renklendirme” yöntemi de iyi. Kırmızı fonksiyonla yeşil fonksiyonun aynı anda etkin olmayacağını biliyorsanız, ikisinin yerel değişkenlerini veya parametrelerini yeniden kullanabilirsiniz

    • Kısıtlı ortamlarda çalışırken, özellikle masaüstü işletim sistemlerinin rahatlığına alışkınsanız C’nin stack kullanımı sezgisel gelmeyebilir
      Geçmişte katıldığım bir mikrodenetleyici kod tabanı projesinde, birkaç geliştirici haftalar boyunca birden fazla alt sistemdeki yakalaması zor hataların izini sürüyordu
      Kodu taşıdığınızda hata da onunla birlikte yer değiştiriyordu. Biraz iz sürüp tuzaklar kurunca, çağrı stack’inin fazla derinleşerek başka veri yapılarını ezdiği kod konumlarını bulabildik
  • Programlamayı ilk öğrenirken tam olarak bu şekilde programlamaya zorlanmıştım. 1970’lerde değil, 2001’de böyleydi
    Çünkü ilk programlama deneyimim, oyun geliştirme aracı RPG Maker 2000’in sunduğu yarı grafiksel betik “dili”ydi
    RM2K betiklemeyi hiç görmediyseniz Scratch ile Emacs Paredit modunun karışımını düşünün. Örnek: https://forums.rpgmakerweb.com/data/attachments/21/21958-f89...
    Metin gibi görünür ama metin gibi düzenleyemezsiniz; yalnızca özellik iletişim kutuları olan bloklar olarak düzenlenir
    Doğal olarak RPG Maker’ın betik dilinde stack gibi havalı şeyler yoktu. Yeniden kullanılabilir bir alt yordam gerekiyorsa parametreler için gizli global değişkenler ayırmanız gerekirdi; reentrant değildi
    Geriye dönüp bakınca, yeterince inat edilseydi RPG Maker 2000 içinde hem register’lar hem de runtime stack uygulanabilirmiş gibi geliyor
    Başta kolay görünüyor. 6502’nin zero page’i gibi sahte “register”lar oluşturabilir, dolaylı değişken erişimiyle (https://rpgmaker.net/tutorials/523/) bir stack de yapabilirsiniz
    Sorun, RM2K’de “parallel process” betikleri biçiminde eşzamanlılığın olması. Bu tür soyutlamaları paralel süreçler kullanınca farklı “thread”ler durumu kafalarına göre birbirinin üzerine yazar
    Bu yüzden her “sanal çekirdek” için birden çok zero page ve stack gerekir; her paralel betiğe de bir sanal çekirdek atamak/bağlamak/zamanlamak gerekir. Yani her betiğin, bir şekilde yalnızca kendisinin bildiği bir stack pointer’a sahip olması gerekir
    Yarış durumlarında da kararlı kılmak için genellikle mutex gibi bir şeye ihtiyaç olur
    RPG Maker oyun geliştiricilerinin inatçılığını düşününce, birilerinin bir runtime özelliğini kandırıp mutex gibi davranmasını sağlamanın yolunu bulmuş olacağını düşünüyorum; ama gerçekten ne yaptıklarını açıkçası bilmek istemeyecek kadar ürkütücü

    • Ben de rpgmaker ile başladım; bunu okuyunca gerçekten nostalji bastı
      rpgmaker.net’ten custom battle system uygulanmış bir oyun indirdiğimi hatırlıyorum. Yerleşik savaş sisteminin tamamını, anlattığınız türden tekniklerle söküp değiştiren bir uygulamaydı
      Editörde açıp nasıl çalıştığına baktığımda tamamen afallamıştım. Yüzlerce “değişken” vardı; yanlış hatırlamıyorsam yalnızca i64’e izin veriyordu, ayrıca yüzlerce “switch” de vardı. Switch’ler boolean’dı
      O zamanlar stack, heap, fonksiyon çağrısı gibi kavramlar hakkında hiçbir fikrim yoktu
      Bunu yapmak ve bakımını/debug’ını yürütmek için ne kadar enerji harcandığını hayal bile edemiyorum
  • Yanlış hatırlamıyorsam ZX81’de BASIC programları yazarken “stack yok”a yakın bir yöntemle yazıyorduk
    1 GOTO 30
    10 LET C = A + B
    20 RETURN
    30 LET A = 1
    40 LET B = 2
    50 GOSUB 10
    60 LET A = C
    70 LET B = 3
    80 GOSUB 10
    90 PRINT C
    RUN
    6
    Yazıda derleyicinin yaptığı işi bizzat yapıyordum yani. Satır numaraları bellek adresleriydi, gizli değişkenler de benden gizli değildi. Çünkü derleyici bendim
    Yorumlayıcının yaptığı tek şey GOSUB’ın dönüş adresini saklamaktı
    Yine de kod sözdizimsel olarak yanlış olabilir ya da belleğim çarpıtıyor olabilir. 40 yıl uzun süre, ama genel fikir doğru
    Ayrıca makinenin içindeki Z80 işlemcide stack yönetim özelliği vardı. BASIC yorumlayıcısı gerçekten çok basitti ama mazereti de vardı: yalnızca 1KB RAM ve OS, yorumlayıcı, her şeyi barındıran 8KB ROM

    • Bu da en azından çağrı stack’i kullanıyor. GOSUB, RETURN’ün başvuracağı satır numarasını ya da başka bir referansı saklar; GOSUB çağrılarını iç içe yaptığınızda birden çok dönüş noktasını hatırlaması gerektiği için bir biçimde stack gerekir
      Ancak bazı BASIC’lerde genel amaçlı stack yerine yalnızca dönüş pointer’larının sabit bir dizisi ve mevcut konum indeksi vardı; örneğin çağrı derinliği 7 ile sabitlenmiş olurdu. Programcı açısından çağrı stack’i gibi davranır
      Elbette birinin stack dediğinde bekleyebileceği yerel değişkenler/parametreler içeren “düzgün” bir stack değil
      BBC BASIC’in varsayılan ortamında iç içe çağrılarda, recursion dahil, neler olduğunu gösteren eğlenceli bir demo yapılabiliyordu. Stack konumunu görüntü belleğinin en üstüne alıp oraya bir şey çizilmemesini sağlarsanız, işlem ilerledikçe stack’in büyüdüğünü görebilirdiniz
      Ekran çözünürlüğü düşük olduğundan 2 baytlık dönüş adresi ekran modu 1 veya 5’te 8 kalın piksel olarak görünürdü. Mod 2’de 4 taneydi ama yanıp sönen renk olduğundan daha kötüydü; mod 0, 3, 4, 6’da ise 16 taneydi, fakat bit düzeyinde bakmak 8 renk tekrarını seçmekten daha zordu
  • Keyfi şekilde genişletilebilen heap’ler olmadan önce programcılar en azından biraz mühendislik muhakemesi yapardı
    Çünkü girdilerin olasılıksal dağılımını hesaba katmaları ve tüm ara depolama alanlarının boyutunu uygun ayarlamaları gerekirdi
    Bu yüzden “BUGS AND LIMITATIONS” ortaya çıktı

    • O eski yöntemler, ne yaptığınıza bağlı olarak bugün de geçerli. Hard real-time sistemlerde dinamik bellek neredeyse hiç kullanılmaz; başlıca nedeni bellek ayırma/serbest bırakma sürelerinin deterministik olmamasıdır
      Bu yüzden her şey derleme zamanında statik olarak ayrılır ve girdinin ne kadar bellek tüketeceğini bilmeniz gerekir
      Ama bellek tüketiminin üst sınırını bilmek, eskiden uygulama programcıları için de normal bir şeydi. Çünkü bellek yetersizliğini asla istemezsiniz
      Günümüzde bellek kullanımını öylece YOLO’ya mı bırakıyoruz diye düşünüyorum
    • Tarihsel olarak GNU’nun büyük hedeflerinden biri de buydu. Çekirdek yardımcı araçlardaki yapay sınırlamaları kaldırmak istiyordu
      Örneğin sed’in maksimum komut uzunluğunun sonlu ve kısa olması gibi sınırlamalara kıyasla büyük bir iyileştirmeydi
    • Aslında hata, insanların bilgisayar programlarına girdi sağlamasına izin vermekteydi
  • O kadar uzun süredir fonksiyonel programlama yapıyorum ki, özyineleme olmadan nasıl kod yazılacağını gerçekten aklıma getirmekte zorlanıyorum
    Özyinelemeli algoritmaları yinelemeli algoritmalara nasıl dönüştüreceğimi teknik olarak biliyorum ve kaynak kısıtlarının büyük olduğu yerlerde bunu yapmışlığım da var, ama sevmiyorum
    Genelde özyinelemeli olanın daha güzel olduğunu ve vakaların %99'unda yeterince hızlı olduğunu düşünüyorum. Derleyici kuyruk özyinelemeyi destekliyorsa bu %100'e yaklaşır, ama daha ilginç işlerin çoğunda zaten yığını kendiniz tutmanız gerekir
    Bazen, ben doğmadan önce insanların bunu nasıl yaptığını öğrenmek için özellikle böyle işler yapıyorum. Ara sıra Commodore 64 oyunlarıyla uğraşıyorum ve hızlı, ucuz, kullanımı kolay donanıma alışmış olduğumuz bu dönemin ne kadar büyük bir lüks olduğunu ciddi biçimde hissediyorum

    • Günümüzün komut setleri kesinlikle çok daha kullanışlı
      Böyle eski makinelerde özyineleme yapmak için yığın mekanizmasını kendiniz oluşturmanız gerekiyordu; yine de global depolama dışında temelde kullanabileceğiniz bir yol olmadığından çözülmesi gereken sorunlar kalıyordu
      O dönemleri yaşadım ama kimseye tavsiye etmek istemem
  • Enhanced GNU Awk'in @let özelliğinde, fonksiyon dışında, örneğin BEGIN veya END blokları içindeki @let blokları için derleyicinin gizli global değişkenler ayırmasına izin verilmişti
    Bu değişkenler bloklar arasında mümkün olduğunca yeniden kullanılır
    $ ./gawk --dump-variables 'BEGIN { @let (a, b, c = 1) { } }'
    $ cat awkvars.out
    $let0001: untyped variable
    $let0002: untyped variable
    $let0003: 1
    ARGC: 1
    ARGIND: 0
    ARGV: array, 1 elements
    BINMODE: 0
    [ .. snip many ]
    https://www.kylheku.com/cgit/egawk/about/

    • O web sitesi benim ISP'm üzerinden çalışmıyor. ping de olmuyor, nc -z 104.37.63.7 443 de olmuyor
      Güncelleme: Güvenlik altyapısı bozulmuş gibi görünüyor. Onun ne olduğunu da bilmiyorum, Twitter da kullanmıyorum. AS'ye bakınca Google Fiber çıkıyor
      Ayrıca dox'lanmak istemem