Jak działa wyrzucanie elementów bezużytecznych w językach, które są kompilowane natywnie?

79

Po przejrzeniu kilku odpowiedzi na temat przepełnienia stosu jest jasne, że niektóre natywnie skompilowane języki mają funkcję usuwania śmieci . Ale nie jest dla mnie jasne, jak dokładnie by to działało.

Rozumiem, jak wyrzucanie elementów bezużytecznych może działać z interpretowanym językiem. Śmieciarka po prostu działałaby obok interpretera i usuwała nieużywane i nieosiągalne obiekty z pamięci programu. Oboje biegną razem.

Jak by to działało z skompilowanymi językami? Rozumiem, że kiedy kompilator skompiluje kod źródłowy do kodu docelowego - w szczególności macierzystego kodu maszynowego - jest to gotowe. Jego praca jest skończona. Jak więc skompilowany program może być również usuwany?

Czy kompilator działa w jakiś sposób z procesorem, gdy program jest wykonywany w celu usuwania obiektów „śmieciowych”? Czy też kompilator zawiera minimalny moduł wyrzucania elementów bezużytecznych w pliku wykonywalnym skompilowanego programu.

Uważam, że moje drugie stwierdzenie miałoby większą ważność niż poprzednie z powodu tego fragmentu tej odpowiedzi na temat przepełnienia stosu :

Jednym z takich języków programowania jest Eiffel. Większość kompilatorów Eiffla generuje kod C ze względu na przenośność. Ten kod C służy do tworzenia kodu maszynowego przez standardowy kompilator C. Implementacje Eiffla zapewniają GC (a czasem nawet dokładne GC) dla tego skompilowanego kodu i nie ma potrzeby używania VM. W szczególności kompilator VisualEiffel generował natywny kod maszynowy x86 bezpośrednio z pełną obsługą GC .

Ostatnia instrukcja wydaje się sugerować, że kompilator zawiera jakiś program w końcowym pliku wykonywalnym, który działa jako śmieciarz podczas działania programu.

Strona na stronie D języka na temat wyrzucania elementów bezużytecznych - która jest natywnie skompilowana i ma opcjonalny moduł wyrzucania elementów bezużytecznych - również wydaje się sugerować, że jakiś program działający w tle działa obok oryginalnego programu wykonywalnego w celu wdrożenia wyrzucania elementów bezużytecznych.

D jest językiem programowania systemowego z obsługą odśmiecania. Zwykle nie jest konieczne jawne zwalnianie pamięci. Po prostu przydziel w razie potrzeby, a moduł odśmiecania będzie okresowo zwracał całą nieużywaną pamięć do puli dostępnej pamięci.

Jeśli metoda wspomniano powyżej jest używany, jak dokładnie to działa? Czy kompilator przechowuje kopię jakiegoś programu do czyszczenia pamięci i wkleja go do każdego generowanego przez siebie pliku wykonywalnego?

Czy też mam błędne myślenie? Jeśli tak, to jakie metody są używane do implementacji czyszczenia pamięci dla skompilowanych języków i jak dokładnie by one działały?

Christian Dean
źródło
1
Byłbym wdzięczny, gdyby bliski głosujący w tym pytaniu mógł dokładnie powiedzieć, co jest nie tak, żebym mógł to naprawić?
Christian Dean
6
Jeśli zaakceptujesz fakt, że GC jest w zasadzie częścią biblioteki wymaganej przez konkretną implementację języka programowania, wówczas sedno twojego pytania nie ma nic wspólnego z GC per se i wszystko, co dotyczy statycznego kontra dynamicznego łączenia .
Theodoros Chatzigiannakis
7
Możesz uznać moduł czyszczenia pamięci za część biblioteki wykonawczej, która implementuje język równoważny malloc().
Barmar
9
Działanie modułu wyrzucania elementów bezużytecznych zależy od właściwości alokatora , a nie modelu kompilacji . Alokator zna każdy przydzielony obiekt; przydzielił im. Teraz potrzebujesz tylko sposobu, aby dowiedzieć się, które obiekty wciąż żyją , a kolektor może cofnąć przydział wszystkich obiektów oprócz nich. Nic w tym opisie nie ma nic wspólnego z modelem kompilacji.
Eric Lippert,
1
GC to funkcja pamięci dynamicznej, a nie interpreter.
Dmitrij Grigoriew

Odpowiedzi:

52

Odśmiecanie w skompilowanym języku działa w taki sam sposób, jak w języku interpretowanym. Języki takie jak Go używają śledzenia zbieraczy śmieci, mimo że ich kod jest zwykle kompilowany z wyprzedzeniem do kodu maszynowego.

(Śledzenie) wyrzucanie elementów bezużytecznych zwykle rozpoczyna się od przejścia stosów wywołań wszystkich aktualnie działających wątków. Obiekty na tych stosach są zawsze żywe. Następnie moduł wyrzucania elementów bezużytecznych przemierza wszystkie obiekty wskazane przez obiekty aktywne, dopóki nie zostanie wykryty cały wykres obiektów aktywnych.

Oczywiste jest, że wykonanie tego wymaga dodatkowych informacji, których nie dostarczają języki takie jak C. W szczególności wymaga mapy ramki stosu każdej funkcji, która zawiera przesunięcia wszystkich wskaźników (i prawdopodobnie ich typów danych), a także mapy wszystkich układów obiektów zawierających te same informacje.

Łatwo jednak zauważyć, że języki, które mają silne gwarancje typu (np. Jeśli rzutowanie wskaźnika na różne typy danych jest niedozwolone) mogą rzeczywiście obliczyć te mapy w czasie kompilacji. Po prostu przechowują powiązanie między adresami instrukcji i mapami ramek stosu oraz powiązanie między typami danych i mapami układu obiektów w pliku binarnym. Informacje te umożliwiają im następnie przejście przez wykres obiektowy.

Sam garbage collector jest niczym więcej niż biblioteką połączoną z programem, podobną do biblioteki standardowej C. Na przykład ta biblioteka może zapewnić funkcję podobną do malloc()tej, która uruchamia algorytm gromadzenia danych, jeśli ciśnienie pamięci jest wysokie.

avdgrinten
źródło
9
Między bibliotekami narzędziowymi a kompilacją JIT linie między „kompilowanym do natywnego” a „uruchomionym w środowisku wykonawczym” stają się coraz bardziej rozmyte.
corsiKa
6
Wystarczy dodać trochę o językach, które nie są obsługiwane przez GC: Prawdą jest, że C i inne takie języki nie dostarczają informacji o stosach połączeń, ale jeśli nie masz nic przeciwko kodowi specyficznemu dla platformy (zwykle zawiera trochę kodu asemblera) nadal można zaimplementować „konserwatywne usuwanie śmieci”. Boehm GC jest przykładem tego wykorzystywane w rzeczywistych programów życiowych.
Matti Virkkunen
2
@corsiKa A raczej linia jest znacznie wyraźniejsza. Teraz widzimy, że są to różne niepowiązane ze sobą pojęcia, a nie antonimy siebie nawzajem.
Kroltan
4
Dodatkowa złożoność, o której musisz pamiętać w skompilowanych i zinterpretowanych środowiskach wykonawczych, dotyczy tego zdania w Twojej odpowiedzi: „(Śledzenie) wyrzucanie elementów bezużytecznych zwykle rozpoczyna się od przejścia stosów wywołań wszystkich aktualnie uruchomionych wątków”. Moje doświadczenie we wdrażaniu GC w skompilowanym środowisku jest takie, że śledzenie stosów nie wystarczy. Punktem wyjścia jest zwykle zawieszanie wątków na wystarczająco długo, aby można było prześledzić ich rejestry , ponieważ mogą one zawierać odwołania w tych rejestrach, które nie zostały jeszcze zapisane na stosie. Dla tłumacza zwykle nie jest to ...
Jules
... problem, ponieważ środowisko może zorganizować GC w „bezpiecznych punktach”, w których interpreter wie, że wszystkie dane są bezpiecznie przechowywane w interpretowanych stosach.
Jules
123

Czy kompilator przechowuje kopię jakiegoś programu do czyszczenia pamięci i wkleja go do każdego generowanego przez siebie pliku wykonywalnego?

Brzmi nieelegancko i dziwnie, ale tak. Kompilator ma całą bibliotekę narzędzi, zawierającą znacznie więcej niż tylko kod czyszczenia pamięci, a wywołania tej biblioteki będą wstawiane do każdego tworzonego pliku wykonywalnego. Nazywa się to biblioteką wykonawczą i zdziwiłbyś się, ile różnych zadań zazwyczaj wykonuje.

Kilian Foth
źródło
51
@ChristianDean Pamiętaj, że nawet C ma bibliotekę wykonawczą. Chociaż nie ma GC, nadal wykonuje zarządzanie pamięcią za pośrednictwem tej biblioteki wykonawczej: malloc()i free()nie są wbudowane w język, nie są częścią systemu operacyjnego, ale są funkcjami w tej bibliotece. C ++ jest czasem kompilowany z biblioteką czyszczenia pamięci, nawet jeśli język nie został zaprojektowany z myślą o GC.
amon
18
C ++ zawiera także bibliotekę wykonawczą, która działa na zasadzie make dynamic_casti wyjątki, nawet jeśli nie dodasz GC.
Sebastian Redl,
23
Biblioteka środowiska wykonawczego niekoniecznie jest kopiowana do każdego pliku wykonywalnego (nazywanego łączeniem statycznym), można się do niej odwoływać (ścieżka do pliku binarnego zawierającego bibliotekę) i uzyskiwać do niej dostęp w czasie wykonywania: jest to połączenie dynamiczne.
mouviciel
16
Kompilator nie jest również wymagany do bezpośredniego przeskakiwania do punktu wejścia programu, bez niczego innego. Zgaduję, że każdy kompilator faktycznie wstawia kilka specyficznych dla platformy kodów inicjalizacyjnych przed wywołaniem main(), i jest całkowicie legalne, powiedzmy, odpalenie wątku GC w tym kodzie. (Zakładając, że GC nie jest wykonywane wewnątrz wywołań alokacji pamięci.) W czasie wykonywania GC naprawdę musi tylko wiedzieć, które części obiektu to wskaźniki lub odwołania do obiektów, a kompilator musi wyemitować kod, aby przetłumaczyć odwołanie do obiektu na wskaźnik jeśli GC przenosi obiekty.
millimoose
15
@millimoose: Tak. Na przykład w GCC ten fragment kodu to crt0.o(co oznacza „ C R un T ime, same podstawy”), który łączy się z każdym programem (lub przynajmniej każdym programem, który nie jest wolnostojący ).
Jörg W Mittag
58

Czy też kompilator zawiera minimalny moduł wyrzucania elementów bezużytecznych w kodzie skompilowanego programu.

To dziwny sposób powiedzenia „kompilator łączy program z biblioteką, która wykonuje odśmiecanie”. Ale tak właśnie się dzieje.

Nie jest to nic specjalnego: kompilatory zwykle łączą tony bibliotek z programami, które kompilują; w przeciwnym razie skompilowane programy nie mogłyby wiele zrobić bez ponownego wdrożenia wielu rzeczy od zera: nawet pisanie tekstu na ekranie / pliku /… wymaga biblioteki.

Ale może GC różni się od tych innych bibliotek, które zapewniają jawne interfejsy API, które wywołuje użytkownik?

Nie: w większości języków biblioteki wykonawcze wykonują wiele zakulisowych prac bez publicznego interfejsu API, poza GC. Rozważ te trzy przykłady:

  1. Propagacja wyjątków i wywoływanie rozwijania / niszczenia stosu.
  2. Dynamiczny przydział pamięci (który zwykle nie polega tylko na wywołaniu funkcji, jak w C, nawet gdy nie ma funkcji wyrzucania elementów bezużytecznych).
  3. Śledzenie informacji o typie dynamicznym (dla rzutów itp.).

Dlatego biblioteka śmieciarek wcale nie jest wyjątkowa, a a priori nie ma nic wspólnego z tym, czy program został skompilowany z wyprzedzeniem.

Konrad Rudolph
źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu do punktów zdobytych i wyjaśnionych w górnej odpowiedzi opublikowanej 3 godziny wcześniej
gnat
11
@gnat Czułem, że jest to przydatne / konieczne, ponieważ najwyższa odpowiedź nie jest wystarczająco silna: wspomina o podobnych faktach, ale nie zwraca uwagi, że wyróżnianie śmiecia to całkowicie sztuczne rozróżnienie. Zasadniczo założenie OP jest błędne, a najlepsza odpowiedź nie wspomina o tym. Mój tak (unikając dość szorstkiego terminu „wadliwy”).
Konrad Rudolph
Nie jest to wcale takie wyjątkowe, ale powiedziałbym, że jest to coś wyjątkowego, ponieważ zwykle ludzie myślą o bibliotekach jako o czymś, co wyraźnie wywołują ze swojego kodu; zamiast implementacji podstawowej semantyki języka. Myślę, że błędnym założeniem OP jest raczej to, że kompilator służy jedynie do tłumaczenia kodu w sposób mniej lub bardziej prosty, a nie instrumentuje go wywołaniami biblioteki, których autor nie określił.
millimoose
7
@millimoose Biblioteki wykonawcze działają za kulisami na wiele sposobów, bez wyraźnej interakcji użytkownika. Rozważ propagację wyjątków i wywołanie rozwijania stosu / wywoływania destruktora. Rozważ dynamiczny przydział pamięci (który zwykle nie polega tylko na wywołaniu funkcji, jak w C, nawet gdy nie ma wyrzucania elementów bezużytecznych). Rozważ obsługę informacji o typie dynamicznym (dla rzutów itp.). Więc GC naprawdę nie jest wyjątkowy.
Konrad Rudolph
3
Tak, przyznaję, że sformułowałem to dziwnie. Było tak po prostu dlatego, że byłem sceptycznie nastawiony do tego, że kompilator faktycznie robi coś takiego. Ale teraz, gdy o tym myślę, ma to większy sens. Kompilator może po prostu połączyć moduł zbierający śmieci jak każdą inną część standardowej biblioteki. Wydaje mi się, że niektóre z moich nieporozumień wynikały z myślenia, że ​​śmieciarz jest tylko częścią implementacji interpretera, a nie odrębnym programem.
Christian Dean
23

Jak by to działało z kompilowanymi językami?

Twoje sformułowanie jest nieprawidłowe. Język programowania jest specyfikacja napisany w jakimś raportu technicznego (za dobry przykład, patrz R5RS ). Mówisz o implementacji określonego języka (jakim jest oprogramowanie).

(niektóre języki programowania mają złe specyfikacje lub nawet brakujące te, lub po prostu jako zgodny z jakiegoś realizacji próbki, jednak jeden język programowania definiuje zachowanie - np ma składnię i semantykę -, to nie produkt, oprogramowanie, ale może być implementowane przez niektóre oprogramowanie; wiele języków programowania ma kilka implementacji; w szczególności „skompilowany” jest przymiotnikiem stosowanym do implementacji - nawet jeśli niektóre języki programowania są łatwiejsze do implementacji przez tłumaczy niż przez kompilatory.)

Rozumiem, że kiedy kompilator skompiluje kod źródłowy do kodu docelowego - w szczególności macierzystego kodu maszynowego - jest to gotowe. Jego praca jest skończona.

Zauważ, że tłumacze i kompilatory mają luźne znaczenie, a niektóre implementacje językowe można uznać za oba. Innymi słowy, istnieje między nimi kontinuum. Przeczytaj najnowszą książkę Dragon Book i pomyśl o kodzie bajtowym , kompilacji JIT , dynamicznie emitującym kodzie C, który jest kompilowany w jakąś „wtyczkę”, a następnie dlopen (3) -ed tym samym procesem (i na obecnych maszynach jest to wystarczająco szybki, aby być kompatybilnym z interaktywna REPL , zobacz to )


Zdecydowanie polecam przeczytanie podręcznika GC . Aby odpowiedzieć, potrzebna jest cała książka . Wcześniej przeczytaj stronę Wikipedii Garbage Collection (którą zakładam przeczytałeś przed przeczytaniem poniżej).

System wykonawczy implementacji skompilowanego języka zawiera moduł czyszczenia pamięci, a kompilator generuje kod, który jest odpowiedni dla tego konkretnego systemu wykonawczego. W szczególności operacje podstawowe alokacji (są kompilowane do kodu maszynowego, który) wywoła (lub może) wywołać system wykonawczy.

Jak więc skompilowany program może być również usuwany?

Po prostu emitując kod maszynowy, który używa (i jest „przyjazny” i „zgodny z”) systemem wykonawczym.

Zauważ, że możesz znaleźć kilka bibliotek śmieci, w szczególności Boehm GC , MPS Ravenbrook , a nawet moje ( nieobsługiwane ) Qish . A kodowanie prostego GC nie jest bardzo trudne (jednak debugowanie jest trudniejsze, a kodowanie konkurencyjnego GC jest trudne ).

W niektórych przypadkach kompilator używałby konserwatywnego GC (takiego jak Boehm GC ). Wtedy nie ma wiele do kodowania. Konserwatywny GC (gdy kompilator wywoła procedurę alokacji lub całą procedurę GC) czasami skanuje cały stos wywołań i zakłada, że ​​dowolna strefa pamięci (pośrednio) dostępna ze stosu wywołań jest aktywna. Nazywa się to konserwatywnym GC, ponieważ informacje o pisaniu są tracone: jeśli liczba całkowita na stosie wywołań będzie wyglądać jak jakiś adres, zostanie zastosowana itd.

W innych (trudniejszych) przypadkach środowisko wykonawcze zapewnia generowanie śmieci w trybie kopiowania (typowym przykładem jest kompilator Ocaml, który kompiluje kod Ocaml do kodu maszynowego przy użyciu takiego GC). Problem polega na znalezieniu dokładnie na stosach połączeń wszystkich wskaźników, a niektóre z nich są przenoszone przez GC. Następnie kompilator generuje metadane opisujące ramki stosu wywołań, których używa środowisko wykonawcze. Więc konwencje telefoniczne i ABI stają specyficzny dla tej realizacji (tj kompilatora) i układu wykonawczego.

W niektórych przypadkach kod maszynowy generowany przez kompilator (w rzeczywistości nawet zamknięcia do niego wskazujące) jest sam śmieciami . Dotyczy to zwłaszcza SBCL (dobrej implementacji Common Lisp), która generuje kod maszynowy dla każdej interakcji REPL . Wymaga to również pewnych metadanych opisujących kod i ramek wywołania używanych w nim.

Czy kompilator przechowuje kopię jakiegoś programu do czyszczenia pamięci i wkleja go do każdego generowanego przez siebie pliku wykonywalnego?

Sortuj Jednak system wykonawczy może być biblioteką współdzieloną itp. Czasami (w Linuksie i kilku innych systemach POSIX) może to być nawet interpreter skryptów, np. Przekazywany do execve (2) z shebangiem . Lub tłumacz ELF , patrz elf (5) i PT_INTERPitp.

BTW, większość kompilatorów dla języka z odśmiecaniem pamięci (i ich systemem uruchomieniowym ) jest dziś wolnym oprogramowaniem . Pobierz kod źródłowy i przestudiuj go.

Basile Starynkevitch
źródło
5
Masz na myśli, że istnieje wiele implementacji języka programowania bez wyraźnej specyfikacji. Tak, zgadzam się z tym. Chodzi mi jednak o to, że język programowania nie jest oprogramowaniem (jak jakiś kompilator lub interpreter). Jest to coś, co ma składnię i semantykę (być może obie są źle zdefiniowane).
Basile Starynkevitch
4
@KonradRudolph: To zależy całkowicie od twojej definicji „formalnej” i „specyfikacji” :-D Istnieje specyfikacja języka programowania Ruby ISO / IEC 30170: 2012 , która określa niewielki podzbiór przecięcia Ruby 1.8 i 1.9. Istnieje pakiet Ruby Spec Suite , zestaw przykładów przypadków granicznych, które służą jako swego rodzaju „specyfikacja wykonywalna”. Następnie Ruby Programming Language autorstwa Davida Flanagana i Yukihiro Matsumoto .
Jörg W Mittag
4
Również Ruby Dokumentacja . Dyskusje o problemach w Ruby Issue Tracker . Dyskusje na temat list mailingowych ruby-core (angielski) i ruby-dev (japoński). Oczekiwania społeczności dotyczące zdrowego rozsądku (np. Array#[]To O (1) najgorszy przypadek, Hash#[]to O (1) zamortyzowany najgorszy przypadek). I na koniec: mózg Matza.
Jörg W Mittag
6
@KonradRudolph: Chodzi o to, że nawet język bez formalnej specyfikacji i tylko jedno uzupełnienie nadal można podzielić na „język” (abstrakcyjne reguły i ograniczenia) i „implementację” (programy przetwarzające kod zgodnie z tymi regułami i ograniczenia). A implementacja wciąż stanowi podstawę specyfikacji, choć trywialnej, a mianowicie: „cokolwiek robi kod, jest specyfikacją”. W końcu tak napisano specyfikację ISO, RubySpec i RDoc: poprzez zabawę w MRI i / lub inżynierii odwrotnej MRI.
Jörg W Mittag
1
Cieszę się, że wychowałeś czeskiego śmieciarza. Poleciłbym to badanie PO, ponieważ jest to doskonały przykład tego, jak proste może być zbieranie śmieci, nawet gdy „przykręca się” do istniejącego kompilatora.
Cort Ammon
6

Istnieją już dobre odpowiedzi, ale chciałbym wyjaśnić niektóre nieporozumienia związane z tym pytaniem.

Nie ma czegoś takiego jak „natywnie skompilowany język” jako taki. Na przykład ten sam kod Java został zinterpretowany (a następnie częściowo skompilowany w czasie wykonywania) na moim starym telefonie (Java Dalvik) i jest (z wyprzedzeniem) skompilowany na moim nowym telefonie (ART).

Różnica między uruchomieniem kodu natywnego i interpretowanego jest o wiele mniej rygorystyczna, niż się wydaje. Obie potrzebują do działania bibliotek wykonawczych i systemu operacyjnego (*). Interpretowany kod wymaga interpretera, ale interpreter jest tylko częścią środowiska wykonawczego. Ale nawet to nie jest ścisłe, ponieważ można zastąpić interpreter kompilatorem (just-in-time). Aby uzyskać maksymalną wydajność, możesz mieć jedno i drugie (środowisko wykonawcze Java na pulpicie zawiera interpreter i dwa kompilatory).

Bez względu na to, jak uruchomić kod, powinien on zachowywać się tak samo. Przydzielanie i zwalnianie pamięci jest zadaniem środowiska wykonawczego (podobnie jak otwieranie plików, uruchamianie wątków itp.). W swoim języku piszesz new X()lub piszesz podobnie. Specyfikacja języka mówi, co powinno się zdarzyć, a środowisko wykonawcze to robi.

Część wolnej pamięci zostaje przydzielona, ​​wywoływany jest konstruktor itp. Gdy nie ma wystarczającej ilości pamięci, wywoływany jest moduł czyszczenia pamięci. Ponieważ jesteś już w środowisku wykonawczym, które jest rodzimym fragmentem kodu, istnienie interpretera nie ma żadnego znaczenia.

Naprawdę nie ma bezpośredniego związku między interpretacją kodu a odśmiecaniem. Po prostu języki niskiego poziomu, takie jak C, zostały zaprojektowane z myślą o szybkości i szczegółowej kontroli wszystkiego, co nie pasuje do idei nienatywnego kodu lub śmietnika. Więc jest tylko korelacja.

Było to bardzo prawdziwe w dawnych czasach, gdy np. Interpreter Java był bardzo wolny, a moduł wyrzucania elementów bezużytecznych. W dzisiejszych czasach sprawy wyglądają zupełnie inaczej i mówienie o tłumaczonym języku straciło sens.


(*) Przynajmniej mówiąc o kodzie ogólnego przeznaczenia, pomijając programy ładujące rozruch i podobne.

maaartinus
źródło
Zarówno Ocaml, jak i SBCL są natywnymi kompilatorami. Więc nie „natywnie kompilowane język” implementacje.
Basile Starynkevitch,
@BasileStarynkevitch WAT? Jak nazywanie niektórych mniej znanych kompilatorów odnosi się do mojej odpowiedzi? Czy SBCL jako kompilator oryginalnie interpretowanego języka nie jest argumentem na korzyść mojego twierdzenia, że ​​rozróżnienie nie ma sensu?
maaartinus
Common Lisp (lub inny język) nie jest interpretowany ani kompilowany. Jest to język programowania (specyfikacja). Jego implementacją może być kompilator, interpreter lub coś pośredniego (np. Interpreter kodu bajtowego). SBCL to interaktywna skompilowana implementacja Common Lisp. Ocaml jest także językiem programowania (z implementacją zarówno interpretera kodu bajtowego, jak i natywnego kompilatora).
Basile Starynkevitch,
@BasileStarynkevitch Tak twierdzę. 1. Nie ma czegoś takiego jak język interpretowany lub kompilowany (chociaż C jest rzadko interpretowany, a LISP rzadko był kompilowany, ale to nie ma znaczenia). 2. Istnieją interpretacje, kompilacje i mieszane implementacje dla najbardziej znanych języków i nie ma języka wykluczającego kompilację lub interpretację.
maaartinus
6
Myślę, że twój argument ma wiele sensu. Kluczową sprawą dla grok jest to, że zawsze uruchamiasz „program macierzysty” lub „nigdy”, jakkolwiek chcesz go zobaczyć. Żaden exe w systemie Windows nie jest sam w sobie wykonywalny; potrzebuje jedynie modułu ładującego i innych funkcji systemu operacyjnego, a właściwie jest również częściowo „interpretowany”. Staje się to bardziej oczywiste w przypadku plików wykonywalnych .net. java myprogjest w takim samym lub małym stopniu natywnym jak grep myname /etc/passwdlub ld.so myprog: Jest to plik wykonywalny (cokolwiek to oznacza), który pobiera argument i wykonuje operacje na danych.
Peter A. Schneider,
3

Szczegóły różnią się między implementacjami, ale ogólnie jest to kombinacja następujących elementów:

  • Biblioteka środowiska wykonawczego, która zawiera GC. Będzie to obsługiwać przydział pamięci i mieć inne punkty wejścia, w tym funkcję „GC_now”.
  • Kompilator utworzy tabele dla GC, aby wiedział, które pola w których typach danych są odwołaniami. Zostanie to również wykonane dla ramek stosu dla każdej funkcji, aby GC mógł śledzić ze stosu.
  • Jeśli GC jest przyrostowy (aktywność GC jest przeplatana z programem) lub współbieżny (działa w osobnym wątku), wówczas kompilator będzie również zawierał specjalny kod obiektowy do aktualizacji struktur danych GC po aktualizacji odniesień. Oba mają podobne problemy dotyczące spójności danych.

W przyrostowym i współbieżnym GC skompilowany kod i GC muszą współpracować, aby zachować niektóre niezmienniki. Na przykład w kolektorze kopiującym GC działa, kopiując dane na żywo z przestrzeni A do przestrzeni B, pozostawiając śmieci. W następnym cyklu zmienia A i B i powtarza. Tak więc jedną zasadą może być zapewnienie, że za każdym razem, gdy program użytkownika spróbuje odwołać się do obiektu w przestrzeni A, zostanie to wykryte, a obiekt zostanie natychmiast skopiowany do przestrzeni B, gdzie program może nadal uzyskać do niego dostęp. Adres przekazujący zostaje pozostawiony w przestrzeni A, aby wskazać GC, że tak się stało, aby wszelkie inne odniesienia do obiektu były aktualizowane w miarę ich śledzenia. Jest to znane jako „bariera odczytu”.

Algorytmy GC były badane od lat 60. XX wieku i istnieje obszerna literatura na ten temat. Google, jeśli chcesz uzyskać więcej informacji.

Paul Johnson
źródło