Czy stosy są jedynym rozsądnym sposobem na uporządkowanie programów?


74

Większość architektur, które widziałem, polegają na stosie wywołań do zapisywania / przywracania kontekstu przed wywołaniami funkcji. Jest to tak powszechny paradygmat, że operacje push i pop są wbudowane w większość procesorów. Czy istnieją systemy działające bez stosu? Jeśli tak, to w jaki sposób działają i do czego służą?


5
Biorąc pod uwagę, jak funkcje mają się zachowywać w językach podobnych do C (tzn. Możesz zagnieżdżać wywołania tak głęboko, jak chcesz i możesz wycofywać się w odwrotnej kolejności), nie jest dla mnie jasne, jak inaczej można zaimplementować wywołania funkcji bez tego, że jest niewiarygodnie nieskuteczny. Możesz np. Zmusić programistę do użycia stylu przekazywania kontynuacji lub innej dziwnej formy programowania, ale z jakiegoś powodu nikt nie wydaje się bardzo pracować z CPS na niskim poziomie.
Kevin

5
GLSL działa bez stosu (podobnie jak inne języki w tym konkretnym nawiasie). Po prostu nie zezwala na połączenia rekurencyjne.
Leushenko,

3
Możesz także zajrzeć do okien rejestru , które są używane przez niektóre architektury RISC.
Mark Booth,

2
@Kevin: „Wcześniejsze kompilatory FORTRAN nie obsługiwały rekurencji w podprogramach. Wczesne architektury komputerowe nie obsługiwały żadnej koncepcji stosu, a kiedy bezpośrednio obsługiwały wywołania podprogramów, miejsce zwrotu często było przechowywane w jednej stałej lokalizacji w sąsiedztwie kodu podprogramu, co nie zezwala na ponowne wywołanie podprogramu przed powrotem wywołania podprogramu. Chociaż nie jest to określone w Fortran 77, wiele kompilatorów F77 obsługuje opcję rekursji jako opcję, podczas gdy stało się standardem w Fortran 90. ” en.wikipedia.org/wiki/Fortran#FORTRAN_II
Mooing Duck

3
Mikrokontroler P8X32A („Śmigło”) nie ma pojęcia stosu w swoim standardowym języku asemblera (PASM). Instrukcje odpowiedzialne za przeskakiwanie również samodzielnie modyfikują instrukcję return w pamięci RAM, aby określić miejsce powrotu - które można wybrać dowolnie. Co ciekawe, język „Spin” (interpretowany język wysokiego poziomu, który działa na tym samym chipie) ma tradycyjną semantykę stosu.
Wossname

Odpowiedzi:


50

(Nieco) popularną alternatywą dla stosu wywołań są kontynuacje .

Na przykład maszyna wirtualna Parrot jest oparta na kontynuacji. Jest całkowicie bez stosu: dane są przechowywane w rejestrach (takich jak Dalvik lub LuaVM, Parrot jest oparty na rejestrach), a przepływ sterowania jest reprezentowany przez kontynuacje (w przeciwieństwie do Dalvik lub LuaVM, które mają stos wywołań).

Inną popularną strukturą danych, używaną zwykle przez maszyny wirtualne Smalltalk i Lisp, jest stos spaghetti, który przypomina swoistą sieć stosów.

Jak zauważył @rwong , styl przekazywania kontynuacji jest alternatywą dla stosu wywołań. Programy napisane w stylu przekazującym kontynuację (lub przekształcone w ten sposób) nigdy nie zwracają, więc nie ma potrzeby stosowania stosu.

Odpowiadając na twoje pytanie z innej perspektywy: możliwe jest posiadanie stosu wywołań bez osobnego stosu, poprzez przydzielenie ramek stosu na stosie. Robią to niektóre implementacje Lisp i Scheme.


4
To zależy od twojej definicji stosu. Nie jestem pewien, czy posiadanie połączonej listy (lub tablicy wskaźników do lub ...) ramek stosu jest tak bardzo „nie stosem”, jak „inną reprezentacją stosu”; a programy w językach CPS (w praktyce) mają tendencję do budowania efektywnie połączonych list kontynuacji, które są bardzo podobne do stosów (jeśli nie, możesz sprawdzić GHC, który wypycha to, co nazywa „kontynuacjami”, na stos liniowy dla wydajności).
Jonathan Cast

6
Programy napisane (lub przekształcone w kontynuację) nigdy nie powracają ” ... brzmi złowieszczo.
Rob Penridge,

5
@RobPenridge: zgadzam się, to trochę tajemnicze. CPS oznacza, że ​​zamiast zwracać, funkcje traktują jako dodatkowy argument kolejną funkcję do wywołania po zakończeniu pracy. Tak więc, gdy wywołujesz funkcję i masz trochę pracy, którą musisz wykonać po wywołaniu funkcji, zamiast czekać na powrót funkcji, a następnie kontynuować pracę, zawijasz pozostałą pracę („kontynuacja” ) do funkcji i przekaż tę funkcję jako dodatkowy argument. Wywołana przez ciebie funkcja wywołuje tę funkcję zamiast powrotu i tak dalej. Żadna funkcja nigdy nie zwraca, po prostu
Jörg W Mittag,

3
… Wywołuje następną funkcję. Dlatego nie potrzebujesz stosu wywołań, ponieważ nigdy nie musisz wracać i przywracać stanu wiązania poprzednio wywoływanej funkcji. Zamiast przenosić przeszłe stany, aby móc do niego powrócić, przenoszą się wokół stanu przyszłego , jeśli wolisz.
Jörg W Mittag,

1
@jcast: Cechą definiującą stos jest IMO, że można uzyskać dostęp tylko do najwyższego elementu. Lista kontynuacji, OTOH, zapewni ci dostęp do wszystkich kontynuacji, a nie tylko do najwyższej ramki stosu. Jeśli masz na przykład wznawiane wyjątki w stylu Smalltalk, musisz być w stanie przejść przez stos bez wyskakiwania go. A kontynuacja języka, przy jednoczesnym zachowaniu znajomości stosu wywołań, prowadzi do stosów spaghetti, które są w zasadzie drzewem stosów, w których kontynuacje „rozwidlają” stos.
Jörg W Mittag,

36

W dawnych czasach procesory nie miały instrukcji stosu, a języki programowania nie obsługiwały rekurencji. Z czasem coraz więcej języków decyduje się na obsługę rekurencji, a sprzęt podąża za pakietem z możliwością alokacji ramek stosu. To wsparcie różniło się znacznie na przestrzeni lat w zależności od procesorów. Niektóre procesory przyjęły rejestry ramki stosu i / lub rejestry wskaźnika stosu; niektóre przyjęte instrukcje, które doprowadziłyby do alokacji ramek stosu w jednej instrukcji.

W miarę postępów procesorów z buforami jednopoziomowymi, a następnie wielopoziomowymi, jedną z kluczowych zalet stosu jest lokalizacja pamięci podręcznej. Wierzchołek stosu prawie zawsze znajduje się w pamięci podręcznej. Ilekroć możesz zrobić coś, co ma duży współczynnik trafień w pamięci podręcznej, jesteś na dobrej drodze dzięki nowoczesnym procesorom. Pamięć podręczna zastosowana do stosu oznacza, że ​​lokalne zmienne, parametry itp. Prawie zawsze znajdują się w pamięci podręcznej i cieszą się najwyższym poziomem wydajności.

Krótko mówiąc, wykorzystanie stosu ewoluowało zarówno w sprzęcie, jak i oprogramowaniu. Istnieją inne modele (na przykład wypróbowano obliczenia przepływu danych przez dłuższy czas), ale lokalizacja stosu sprawia, że ​​działa naprawdę dobrze. Co więcej, kod proceduralny jest dokładnie tym, czego oczekują procesory, dla wydajności: jedna instrukcja mówi mu, co robić po drugiej. Gdy instrukcje nie są uporządkowane liniowo, procesor co najmniej ogromnie zwalnia, ponieważ nie ustaliliśmy, jak uzyskać dostęp losowy tak szybko, jak dostęp sekwencyjny. (Przy okazji, istnieją podobne problemy na każdym poziomie pamięci, od pamięci podręcznej, do pamięci głównej, na dysku ...)

Pomiędzy zademonstrowaną wydajnością instrukcji sekwencyjnego dostępu a korzystnym zachowaniem buforowania stosu wywołań, mamy, przynajmniej obecnie, zwycięski model wydajności.

(Możemy również wrzucić zmienność struktur danych do prac ...)

Nie oznacza to, że inne modele programowania nie mogą działać, zwłaszcza gdy można je przełożyć na instrukcje sekwencyjne i model stosu wywołań współczesnego sprzętu. Istnieje jednak wyraźna zaleta dla modeli obsługujących miejsce, w którym znajduje się sprzęt. Jednak rzeczy nie zawsze pozostają takie same, więc mogliśmy zobaczyć zmiany w przyszłości, ponieważ różne technologie pamięci i tranzystorów pozwalają na większą równoległość. Zawsze jest to żart pomiędzy językami programowania a możliwościami sprzętowymi, więc zobaczymy!


9
W rzeczywistości procesory graficzne wciąż nie mają stosów. Zabronione jest ci rekursowanie w GLSL / SPIR-V / OpenCL (nie jestem pewien co do HLSL, ale prawdopodobnie nie widzę powodu, dla którego miałoby być inaczej). Sposób, w jaki faktycznie obsługują „stosy” wywołań funkcji, polega na użyciu absurdalnie dużej liczby rejestrów.
LinearZoetrope

@Jsor: To w dużej mierze szczegół implementacji, jak widać z architektury SPARC. Podobnie jak układy GPU, SPARC ma ogromny zestaw rejestrów, ale jest wyjątkowy, ponieważ ma przesuwane okno, które po zawinięciu przenosi bardzo stare rejestry na stos w pamięci RAM. To naprawdę hybryda między dwoma modelami. I SPARC nie określił dokładnie, ile jest rejestrów fizycznych, jak duże było okno rejestru, więc różne implementacje mogły leżeć w dowolnym miejscu w tej skali „ogromnej liczby rejestrów” do „wystarczającej dla jednego okna, przy każdym wywołaniu funkcji rozlanie bezpośrednio na stos ”
MSalters

Wadą modelu stosu wywołań jest to, że należy bardzo uważnie obserwować przepełnienie tablicy i / lub adresu, ponieważ programy samodostosowujące się jako exploit są możliwe, jeśli można wykonać dowolne bity stosu.
BenPen

14

TL; DR

  • Stos wywołań jako mechanizm wywoływania funkcji:
    1. Zwykle jest symulowany sprzętowo, ale nie ma fundamentalnego znaczenia dla konstrukcji sprzętu
    2. Ma fundamentalne znaczenie dla programowania imperatywnego
    3. Nie ma fundamentalnego znaczenia dla programowania funkcjonalnego
  • Stos jako abstrakcja „ostatnie wejście, pierwsze wyjście” (LIFO) ma fundamentalne znaczenie dla informatyki, algorytmów, a nawet niektórych domen nietechnicznych.
  • Niektóre przykłady organizacji programu, które nie używają stosów połączeń:
    • Styl przekazywania kontynuacji (CPS)
    • Maszyna stanowa - gigantyczna pętla, z wszystko wstawionymi. (Podobno zainspirowany architekturą oprogramowania układowego Saab Gripen i przypisany komunikacji Henry'ego Spencera i odtworzony przez Johna Carmacka.) (Uwaga 1)
    • Architektura przepływu danych - sieć aktorów połączonych kolejkami (FIFO). Kolejki są czasami nazywane kanałami.

Reszta tej odpowiedzi jest przypadkowym zbiorem myśli i anegdot, a zatem nieco chaotycznym.


Opisany przez ciebie stos (jako mechanizm wywoływania funkcji) jest specyficzny dla programowania imperatywnego.

Poniżej programowania imperatywnego znajdziesz kod maszynowy. Kod maszynowy może emulować stos wywołań, wykonując niewielką sekwencję instrukcji.

Poniżej kodu maszynowego znajduje się sprzęt odpowiedzialny za uruchamianie oprogramowania. Podczas gdy nowoczesny mikroprocesor jest zbyt skomplikowany, aby go tu opisać, można sobie wyobrazić, że istnieje bardzo prosta konstrukcja, która jest powolna, ale wciąż jest w stanie wykonać ten sam kod maszynowy. Taki prosty projekt wykorzysta podstawowe elementy logiki cyfrowej:

  1. Logika kombinacyjna, tj. Połączenie bramek logicznych (i, lub nie, ...) Należy zauważyć, że „logika kombinacyjna” wyklucza sprzężenia zwrotne.
  2. Pamięć, tj. Przerzutniki, zatrzaski, rejestry, SRAM, DRAM itp.
  3. Maszyna stanu, która składa się z logiki kombinacyjnej i pewnej pamięci, wystarczającej do implementacji „kontrolera” zarządzającego resztą sprzętu.

Poniższe dyskusje zawierały wiele przykładów alternatywnych sposobów konstruowania programów imperatywnych.

Struktura takiego programu będzie wyglądać następująco:

void main(void)
{
    do
    {
        // validate inputs for task 1
        // execute task 1, inlined, 
        // must complete in a deterministically short amount of time
        // and limited to a statically allocated amount of memory
        // ...
        // validate inputs for task 2
        // execute task 2, inlined
        // ...
        // validate inputs for task N
        // execute task N, inlined
    }
    while (true);
    // if this line is reached, tell the programmers to prepare
    // themselves to appear before an accident investigation board.
    return 0; 
}

Ten styl byłby odpowiedni dla mikrokontrolerów, tj. Dla tych, którzy postrzegają oprogramowanie jako uzupełnienie funkcji sprzętu.



@Peteris: Stosy to struktury danych LIFO.
Christopher Creutzig

1
Ciekawy. Myślałbym to na odwrót. Na przykład FORTRAN jest imperatywnym językiem programowania, a wczesne wersje nie używały stosu wywołań. Jednak rekurencja ma fundamentalne znaczenie dla programowania funkcjonalnego i nie sądzę, że można ją zaimplementować w ogólnym przypadku bez użycia stosu.
TED

@TED ​​- w implementacji języka funkcjonalnego znajduje się struktura danych stosu (lub zazwyczaj drzewa) reprezentująca oczekujące obliczenia, ale niekoniecznie wykonuje się go za pomocą instrukcji przy użyciu trybów adresowania zorientowanych na stosie maszyny, a nawet instrukcji wywołania / powrotu (w sposób zagnieżdżony / rekurencyjny - może tylko jako część pętli automatu stanów).
davidbak

@davidbak - IIRC, algorytm rekurencyjny musi być rekurencyjny, aby móc pozbyć się stosu. Prawdopodobnie istnieją inne przypadki, w których można to zoptymalizować, ale w ogólnym przypadku musisz mieć stos . W rzeczywistości powiedziano mi, że istnieje matematyczny dowód tego, że gdzieś się unosi. Ta odpowiedź twierdzi, że jest to twierdzenie Church-Turinga (myślę, że oparte na fakcie, że maszyny Turinga używają stosu?)
TED

1
@TED ​​- Zgadzam się z tobą. Uważam, że nieporozumienie polega na tym, że przeczytałem post OP, aby mówić o architekturze systemu, która oznaczała dla mnie architekturę maszyny . Myślę, że inni, którzy tu odpowiedzieli, mają to samo zrozumienie. Więc ci z nas, którzy rozumieli że to kontekst, odpowiedzieli, że nie potrzebujesz stosu na poziomie instrukcji maszyny / trybu adresowania. Widzę jednak, że pytanie to można również interpretować w ten sposób, że system językowy potrzebuje stosu wywołań. Ta odpowiedź również brzmi „ nie”, ale z różnych powodów.
davidbak

11

Nie, niekoniecznie.

Czytaj stary papier Appela Odśmiecanie może być szybsze niż alokacja stosu . Wykorzystuje styl przekazywania kontynuacji i pokazuje implementację bez stosu.

Zauważ też, że stare architektury komputerów (np. IBM / 360 ) nie miały żadnego rejestru stosu sprzętu. Ale system operacyjny i kompilator zarezerwowały rejestr wskaźnika stosu zgodnie z konwencją (związaną z konwencjami wywoływania ), aby mogły mieć stos wywołań programowych .

Zasadniczo cały kompilator i optymalizator programu C może wykryć przypadek (dość powszechny w systemach osadzonych), w którym wykres wywołań jest statycznie znany i nie ma rekurencji (ani wskaźników funkcji). W takim systemie każda funkcja mogła zachować swój adres zwrotny w stałej, statycznej lokalizacji (i tak działał Fortran77 w komputerach z 1970 roku).

Obecnie procesory mają również stosy wywołań (oraz instrukcje maszynowe wywoływania i zwracania) rozpoznające pamięć podręczną procesora .


1
Jestem całkiem pewien, że FORTRAN przestał używać statycznych lokalizacji powrotu, gdy FORTRAN-66 wyszedł i wymagał wsparcia dla SUBROUTINEi FUNCTION. Ale masz rację dla wcześniejszych wersji (FORTRAN-IV i prawdopodobnie WATFIV).
TMN,

I oczywiście COBOL. I doskonała uwaga na temat IBM / 360 - ma dość duże zastosowanie, mimo że brakuje trybów adresowania stosu sprzętu. (R14, wydaje mi się, że tak było?) I miał kompilatory dla języków opartych na stosie, np. PL / I, Ada, Algol, C.
davidbak,

Rzeczywiście, studiowałem 360 w college'u i na początku uznałem to za oszałamiające.
JDługosz

1
@ JDługosz Najlepszym sposobem, aby współczesni studenci architektury komputerowej mogli wziąć pod uwagę 360, jest bardzo prosta maszyna RISC ... choć z więcej niż jednym formatem instrukcji ... i kilkoma anomaliami takimi jak TRi TRT.
davidbak

Co powiesz na „zero i dodaj spakowane”, aby przenieść rejestr? Ale „rozgałęzienie i łącze” zamiast stosu adresu zwrotnego powróciło.
JDługosz

10

Jak dotąd masz kilka dobrych odpowiedzi; dam ci niepraktyczny, ale bardzo edukacyjny przykład, w jaki sposób możesz zaprojektować język bez pojęcia stosów lub „kontroli przepływu”. Oto program, który określa silnie:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = f(3)

Umieszczamy ten program w ciągu i oceniamy go przez podstawienie tekstowe. Więc kiedy oceniamyf(3) , przeprowadzamy wyszukiwanie i zastępujemy 3 dla i, w ten sposób:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = if 3 == 0 then 1 else 3 * f(3 - 1)

Wspaniały. Teraz wykonujemy kolejne podstawienie tekstu: widzimy, że warunek „if” jest fałszywy i zastępujemy kolejny ciąg znaków, tworząc program:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(3 - 1)

Teraz wykonujemy kolejny ciąg zastępujący wszystkie podwyrażenia obejmujące stałe:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(2)

I widzicie, jak to idzie; Nie będę dalej pracował nad tym. Moglibyśmy nadal wykonywać serię zamian łańcuchów, aż do tego dojdziemy let x = 6i skończymy.

Stosu tradycyjnie używamy do zmiennych lokalnych i informacji o kontynuacji; pamiętaj, że stos nie mówi, skąd przyszedłeś, ale wskazuje, dokąd zmierzasz z tą wartością zwrotną w ręku.

W modelu programowania łańcuchowego podstawiania łańcuchów nie ma „zmiennych lokalnych” na stosie; parametry formalne są zastępowane ich wartościami, gdy funkcja jest zastosowana do jej argumentu, a nie umieszczana w tabeli odnośników na stosie. I nie ma „gdzieś dalej”, ponieważ ewaluacja programu polega po prostu na zastosowaniu prostych reguł do podstawiania łańcuchów w celu stworzenia innego, ale równoważnego programu.

Teraz, oczywiście, faktycznie zastępowanie ciągów prawdopodobnie nie jest dobrym rozwiązaniem. Jednak języki programowania, które obsługują „rozumowanie równe” (takie jak Haskell) logicznie korzystają z tej techniki.


3
Retina jest przykładem języka programowania opartego na Regex, który do obliczeń wykorzystuje operacje łańcuchowe.
Andrew Piliser,

2
@AndrewPiliser Zaprojektowany i wdrożony przez tego fajnego kolesia .
kot

3

Od czasu publikacji przez Parnas w 1972 roku On kryteriów, które należy stosować przy rozkładaniu systemów na moduły zasadnie przyjęto, że ukrywanie informacji w oprogramowaniu jest dobrą rzeczą. Wynika to z długiej debaty w latach 60. na temat rozkładu strukturalnego i programowania modułowego.

Modułowość

Niezbędny wynik relacji czarnej skrzynki między modułami zaimplementowanymi przez różne grupy w dowolnym systemie wielowątkowym wymaga mechanizmu umożliwiającego ponowne wprowadzenie i środków do śledzenia dynamicznego grafu połączeń systemu. Kontrolowany przepływ wykonania musi przechodzić zarówno do, jak i do wielu modułów.

Dynamiczne określanie zakresu

Gdy tylko zakres leksykalny jest niewystarczający, aby śledzić zachowanie dynamiczne, wymagane jest pewne prowadzenie księgowości w czasie wykonywania w celu śledzenia różnicy.

Biorąc pod uwagę, że każdy wątek (z definicji) ma tylko jeden bieżący wskaźnik instrukcji, stos LIFO jest odpowiedni do śledzenia każdego wywołania.

Wyjątki

Tak więc, podczas gdy model kontynuacji nie utrzymuje struktury danych wprost dla stosu, nadal istnieje zagnieżdżone wywołanie modułów, które należy gdzieś utrzymywać!

Nawet języki deklaratywne zachowują historię oceny lub odwrotnie spłaszczają plan wykonania ze względu na wydajność i utrzymują postęp w inny sposób.

Struktura nieskończonej pętli identyfikowana przez rwong jest powszechna w aplikacjach o wysokiej niezawodności ze statycznym planowaniem, które uniemożliwia wiele popularnych struktur programowania, ale wymaga, aby całą aplikację traktować jako białą skrzynkę bez znaczącego ukrywania informacji.

Wiele współbieżnych nieskończonych pętli nie wymaga żadnej struktury do przechowywania adresów zwrotnych, ponieważ nie wywołują one funkcji, co sprawia, że ​​pytanie jest dyskusyjne. Jeśli komunikują się przy użyciu wspólnych zmiennych, mogą one łatwo przekształcić się w starsze analogiczne adresy zwrotne w stylu Fortran.


1
Malujesz się w kącie, zakładając „ dowolny system wielowątkowy”. Sprzężone maszyny o stanie skończonym mogą mieć wiele wątków w swojej implementacji, ale nie wymagają stosu LIFO. W FSM nie ma żadnych ograniczeń, że powracasz do dowolnego poprzedniego stanu, nie mówiąc już o kolejności LIFO. Więc to jest prawdziwy wielowątkowy system, do którego się nie nadaje. A jeśli ograniczysz się do definicji wielowątkowej jako „stosy niezależnych równoległych wywołań funkcji”, otrzymasz definicję kołową.
MSalters

Nie czytam w ten sposób pytania. OP zna wywołania funkcji, ale pyta o inne systemy.
MSalters

@MSalters Zaktualizowano, aby uwzględnić współbieżne nieskończone pętle. Model jest poprawny, ale ogranicza skalowalność. Sugerowałbym, że nawet umiarkowane maszyny stanowe zawierają wywołania funkcji, aby umożliwić ponowne użycie kodu.
Pekka

2

Wszystkie stare komputery mainframe (IBM System / 360) w ogóle nie miały pojęcia o stosie. Na przykład na 260 parametry zostały zbudowane w ustalonym miejscu w pamięci, a kiedy wywołano podprogram, wywołano go R1wskazując na blok parametrów i R14zawierający adres zwrotny. Wywoływana procedura, jeśli chce wywołać inny podprogram, musiałaby zapisaćR14 w znanym miejscu przed wykonaniem tego połączenia.

Jest to o wiele bardziej niezawodne niż stos, ponieważ wszystko można przechowywać w ustalonych lokalizacjach pamięci ustalonych w czasie kompilacji i można w 100% zagwarantować, że procesy nigdy nie zabraknie stosu. W dzisiejszych czasach nie ma żadnego z „Przydziel 1 MB i trzymajcie kciuki”.

Rekurencyjne wywołania podprogramów były dozwolone w PL / I przez określenie słowa kluczowego RECURSIVE. Oznaczały one, że pamięć używana przez podprogram była dynamicznie przydzielana, a nie statycznie przydzielana. Ale rekurencyjne połączenia były wtedy tak rzadkie, jak teraz.

Operacja bez stosu sprawia, że ​​masowe wielowątkowość jest znacznie łatwiejsze, dlatego często podejmowane są próby uczynienia współczesnych języków pozbawionymi stali. Nie ma na przykład żadnego powodu, dla którego kompilator C ++ nie może być modyfikowany przez back-end, aby używać dynamicznie alokowanej pamięci zamiast stosów.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.