Wie funktioniert die Speicherbereinigung in Sprachen, die ursprünglich kompiliert wurden?

79

Nach dem Durchsuchen mehrerer Antworten eines Stapelüberlaufs ist klar, dass einige nativ kompilierte Sprachen eine Garbage Collection haben . Aber mir ist unklar, wie genau das funktionieren würde.

Ich verstehe, wie Garbage Collection mit einer interpretierten Sprache funktionieren kann. Der Garbage Collector wird einfach neben dem Interpreter ausgeführt und löscht nicht verwendete und nicht erreichbare Objekte aus dem Speicher des Programms. Sie laufen beide zusammen.

Wie würde das mit kompilierten Sprachen funktionieren? Ich verstehe, dass, sobald der Compiler den Quellcode in den Zielcode kompiliert hat - insbesondere nativen Maschinencode -, es getan ist. Seine Arbeit ist beendet. Also, wie könnte das kompilierte Programm auch als Müll eingesammelt werden?

Arbeitet der Compiler in irgendeiner Weise mit der CPU zusammen, während das Programm zum Löschen von "Garbage" -Objekten ausgeführt wird? Oder enthält der Compiler einen minimalen Garbage Collector in der ausführbaren Datei des kompilierten Programms.

Ich glaube, meine letztere Aussage hätte aufgrund dieses Auszugs aus dieser Antwort zum Stapelüberlauf mehr Gültigkeit als die erstere :

Eine solche Programmiersprache ist Eiffel. Die meisten Eiffel-Compiler generieren aus Gründen der Portabilität C-Code. Dieser C-Code wird verwendet, um Maschinencode von einem Standard-C-Compiler zu erzeugen. Eiffel-Implementierungen bieten GC (und manchmal sogar genaue GC) für diesen kompilierten Code, und es ist keine VM erforderlich. Insbesondere hat der VisualEiffel-Compiler nativen x86-Maschinencode direkt mit vollständiger GC-Unterstützung generiert .

Die letzte Aussage scheint zu implizieren, dass der Compiler ein Programm in die endgültige ausführbare Datei einschließt, das als Garbage Collector fungiert, während das Programm ausgeführt wird.

Die Seite auf der D-Language-Website über die Garbage Collection - die nativ kompiliert wurde und optional über einen Garbage Collector verfügt - scheint auch darauf hinzudeuten, dass einige Hintergrundprogramme neben dem ursprünglichen ausführbaren Programm zur Implementierung der Garbage Collection ausgeführt werden.

D ist eine Systemprogrammiersprache mit Unterstützung für die Garbage Collection. Normalerweise ist es nicht erforderlich, Speicher explizit freizugeben. Ordnen Sie es einfach nach Bedarf zu, und der Garbage Collector gibt in regelmäßigen Abständen den gesamten nicht verwendeten Speicher in den Pool des verfügbaren Speichers zurück.

Wenn die oben genannten Verfahren wird verwendet, wie genau würde es funktionieren? Speichert der Compiler eine Kopie eines Garbage Collection-Programms und fügt sie in jede von ihm generierte ausführbare Datei ein?

Oder habe ich einen Denkfehler? Wenn ja, welche Methoden werden zur Implementierung der Garbage Collection für kompilierte Sprachen verwendet und wie genau würden sie funktionieren?

Christian Dean
quelle
1
Ich würde es begrüßen, wenn der enge Wähler dieser Frage genau sagen könnte, was falsch ist, damit ich es beheben kann.
Christian Dean
6
Wenn Sie die Tatsache akzeptieren, dass der GC im Grunde genommen Teil einer Bibliothek ist, die für eine bestimmte Programmiersprachenimplementierung erforderlich ist, hat der Kern Ihrer Frage nichts mit dem GC an sich zu tun und alles, was mit statischer oder dynamischer Verknüpfung zu tun hat .
Theodoros Chatzigiannakis
7
Sie können den Garbage Collector als Teil der Laufzeitbibliothek betrachten, die die Entsprechung der Sprache zu implementiert malloc().
Barmar
9
Der Betrieb eines Garbage Collectors hängt von den Merkmalen des Allokators ab , nicht vom Kompilierungsmodell . Der Zuweiser kennt jedes Objekt, das zugewiesen wurde. es teilte sie zu. Alles was Sie jetzt brauchen , ist eine Möglichkeit , zu wissen , welche Objekte sind noch am Leben , und der Kollektor kann alle Objekte außer sie freigeben. Nichts in dieser Beschreibung hat etwas mit dem Kompilierungsmodell zu tun.
Eric Lippert
1
GC ist eine Funktion des dynamischen Speichers, keine Funktion des Interpreters.
Dmitry Grigoryev

Antworten:

52

Die Garbage Collection in einer kompilierten Sprache funktioniert genauso wie in einer interpretierten Sprache. Sprachen wie Go verwenden Tracing-Garbage-Collectors, obwohl ihr Code normalerweise im Voraus kompiliert wird, um Code zu verarbeiten.

Die (Ablaufverfolgungs-) Speicherbereinigung beginnt normalerweise mit dem Durchlaufen der Aufrufstapel aller derzeit ausgeführten Threads. Objekte auf diesen Stapeln sind immer aktiv. Danach durchläuft der Garbage Collector alle Objekte, auf die von Live-Objekten verwiesen wird, bis der gesamte Live-Objekt-Graph erkannt wurde.

Es ist klar, dass dies zusätzliche Informationen erfordert, die Sprachen wie C nicht bieten. Insbesondere ist eine Karte des Stapelrahmens jeder Funktion erforderlich, die die Offsets aller Zeiger (und wahrscheinlich ihrer Datentypen) sowie Karten aller Objektlayouts enthält, die dieselben Informationen enthalten.

Es ist jedoch leicht zu erkennen, dass Sprachen mit starken Typgarantien (z. B. wenn Zeigerverbindungen zu verschiedenen Datentypen nicht zulässig sind) diese Maps beim Kompilieren tatsächlich berechnen können. Sie speichern einfach eine Zuordnung zwischen Anweisungsadressen und Stack-Frame-Maps und eine Zuordnung zwischen Datentypen und Objektlayout-Maps innerhalb der Binärdatei. Diese Informationen ermöglichen es ihnen dann, die Objektgraphendurchquerung durchzuführen.

Der Garbage Collector selbst ist nichts anderes als eine mit dem Programm verknüpfte Bibliothek, ähnlich der C-Standardbibliothek. Diese Bibliothek könnte beispielsweise eine ähnliche Funktion bereitstellen malloc(), die den Erfassungsalgorithmus ausführt, wenn der Speicherdruck hoch ist.

avdgrinten
quelle
9
Zwischen Dienstprogrammbibliotheken und JIT-Kompilierung verschwimmen die Grenzen zwischen "Compiled to Native" und "Läuft in einer Laufzeitumgebung" immer mehr.
corsiKa
6
Nur um ein wenig über Sprachen, die nicht mit GC-Unterstützung geliefert werden, hinzuzufügen: Es ist richtig, dass C und andere solche Sprachen keine Informationen über Call-Stacks liefern, aber wenn Sie mit plattformspezifischem Code einverstanden sind (normalerweise mit ein wenig Code) Assembler-Code) ist es immer noch möglich, "konservative Garbage Collection" zu implementieren. Der Boehm GC ist ein Beispiel dafür, wie er in realen Programmen verwendet wird.
Matti Virkkunen
2
@corsiKa Oder besser gesagt, die Linie ist viel ausgeprägter. Jetzt sehen wir, dass dies verschiedene, nicht miteinander verbundene Konzepte sind und keine Antonyme voneinander.
Kroltan
4
Eine zusätzliche Komplexität, die Sie bei kompilierten und interpretierten Laufzeiten beachten müssen, bezieht sich auf diesen Satz in Ihrer Antwort: "Die (Ablaufverfolgungs-) Speicherbereinigung beginnt normalerweise damit, die Aufrufstapel aller derzeit ausgeführten Threads zu durchsuchen." Nach meiner Erfahrung mit der Implementierung von GC in einer kompilierten Umgebung reicht es nicht aus, die Stapel zu verfolgen. Der Ausgangspunkt besteht normalerweise darin, die Threads so lange anzuhalten , dass sie von ihren Registern zurückverfolgt werden können , da sie möglicherweise Referenzen in den Registern haben, die noch nicht im Stapel gespeichert wurden. Für einen Dolmetscher ist dies normalerweise nicht ...
Jules
... ein Problem, da die Umgebung dafür sorgen kann, dass die GC an "sicheren Punkten" stattfindet, an denen der Dolmetscher weiß, dass alle Daten sicher in den interpretierten Stapeln gespeichert sind.
Jules
123

Speichert der Compiler eine Kopie eines Garbage Collection-Programms und fügt sie in jede von ihm generierte ausführbare Datei ein?

Es klingt unelegant und komisch, aber ja. Der Compiler verfügt über eine vollständige Dienstprogrammbibliothek, die viel mehr als nur Garbage Collection-Code enthält. Aufrufe dieser Bibliothek werden in jede von ihm erstellte ausführbare Datei eingefügt. Dies wird als Laufzeitbibliothek bezeichnet , und Sie werden überrascht sein, wie viele verschiedene Aufgaben normalerweise ausgeführt werden.

Kilian Foth
quelle
51
@ChristianDean Beachten Sie, dass auch C eine Laufzeitbibliothek hat. Es ist zwar nicht GC hat, führt er immer noch Speicherverwaltung durch diese Laufzeitbibliothek: malloc()und free()sind in der Sprache nicht gebaut, sind nicht Teil des Betriebssystems, sondern sind Funktionen in dieser Bibliothek. C ++ wird manchmal auch mit einer Garbage Collection-Bibliothek kompiliert, obwohl die Sprache nicht für GC entwickelt wurde.
amon
18
C ++ enthält auch eine Laufzeitbibliothek, mit der Dinge wie make dynamic_castund Ausnahmen funktionieren, auch wenn Sie keinen GC hinzufügen.
Sebastian Redl
23
Die Laufzeitbibliothek muss nicht unbedingt in jede ausführbare Datei kopiert werden (die als statische Verknüpfung bezeichnet wird). Sie kann nur referenziert werden (ein Pfad zu der Binärdatei, die die Bibliothek enthält) und zur Ausführungszeit aufgerufen werden. Dies ist eine dynamische Verknüpfung.
Mouviciel
16
Der Compiler muss auch nicht direkt in den Eintrittspunkt Ihres Programms springen, ohne dass etwas anderes passiert. Ich gehe davon aus, dass jeder Compiler tatsächlich eine Reihe plattformspezifischer Initialisierungscodes einfügt, bevor er aufruft main(), und es ist durchaus zulässig, beispielsweise einen GC-Thread in diesem Code zu starten. (Vorausgesetzt, der GC wird nicht innerhalb von Speicherzuweisungsaufrufen ausgeführt.) Zur Laufzeit muss der GC nur wirklich wissen, welche Teile eines Objekts Zeiger oder Objektreferenzen sind, und der Compiler muss den Code ausgeben, um eine Objektreferenz auf einen Zeiger zu übersetzen wenn der GC Objekte verschiebt.
Millimoose
15
@millimoose: Ja. In GCC ist dieser Code beispielsweise crt0.o(der für " C R un T ime, das Wesentliche" steht), der mit jedem Programm (oder zumindest jedem Programm, das nicht frei verfügbar ist ) verknüpft wird .
Jörg W Mittag
58

Oder enthält der Compiler einige minimale Garbage Collector im Code des kompilierten Programms.

Das ist eine seltsame Art zu sagen: "Der Compiler verknüpft das Programm mit einer Bibliothek, die die Garbage Collection durchführt." Aber ja, genau das passiert.

Das ist nichts Besonderes: Compiler verknüpfen normalerweise Tonnen von Bibliotheken mit den Programmen, die sie kompilieren. Andernfalls könnten kompilierte Programme nicht viel tun, ohne viele Dinge von Grund auf neu zu implementieren: Selbst das Schreiben von Text auf den Bildschirm / eine Datei / ... erfordert eine Bibliothek.

Aber vielleicht unterscheidet sich GC von diesen anderen Bibliotheken, die explizite APIs bereitstellen, die der Benutzer aufruft?

Nein: In den meisten Sprachen arbeiten die Laufzeitbibliotheken viel hinter den Kulissen ohne öffentlich zugängliche API, über GC hinaus. Betrachten Sie diese drei Beispiele:

  1. Exception Propagation und Stack Unwinding / Destructor Calling.
  2. Dynamische Speicherzuweisung (die normalerweise nicht nur eine Funktion wie in C aufruft, auch wenn keine Garbage Collection vorhanden ist).
  3. Verfolgung von dynamischen Typinformationen (für Casts usw.).

Eine Garbage Collection Library ist also nichts Besonderes, und a priori hat nichts damit zu tun, ob ein Programm im Voraus kompiliert wurde.

Konrad Rudolph
quelle
dies scheint nicht alles wesentliche über Punkte gemacht zu bieten und erklärte in Top-Antwort gepostet 3 Stunden vor
gnat
11
@gnat Ich fand es nützlich / notwendig, weil die Top-Antwort bei weitem nicht stark genug ist: Es werden ähnliche Fakten erwähnt, aber es wird nicht darauf hingewiesen, dass das Herausgreifen der Garbage Collection eine völlig künstliche Unterscheidung ist. Grundsätzlich ist die Annahme von OP fehlerhaft, und die Top-Antwort erwähnt dies nicht. Meins tut es (unter Vermeidung des eher brüsken Begriffs „fehlerhaft“).
Konrad Rudolph
Es ist nicht besonders, aber ich würde sagen, es ist etwas Besonderes, da die Leute Bibliotheken normalerweise als etwas betrachten, das sie explizit aus ihrem Code aufrufen. eher als eine Implementierung grundlegender Sprachsemantik. Ich denke, die falsche Annahme des OP ist eher, dass ein Compiler Code nur mehr oder weniger einfach übersetzen soll, anstatt ihn mit Bibliotheksaufrufen zu instrumentieren, die der Autor nicht spezifiziert hat.
Millimoose
7
@millimoose Runtime-Bibliotheken arbeiten auf vielfältige Weise ohne explizite Benutzerinteraktion hinter den Kulissen. Berücksichtigen Sie die Weitergabe von Ausnahmen und den Aufruf von Stack Unwinding / Destructor. Berücksichtigen Sie die dynamische Speicherzuordnung (die normalerweise nicht nur eine Funktion wie in C aufruft, auch wenn keine Garbage Collection vorhanden ist). Erwägen Sie den Umgang mit dynamischen Typinformationen (für Casts usw.). Der GC ist also wirklich kein Einzelfall.
Konrad Rudolph
3
Ja, ich gebe zu, dass ich das merkwürdig formuliert hatte. Das lag einfach daran, dass ich skeptisch war, dass der Compiler tatsächlich so etwas tat. Aber jetzt, da ich darüber nachdenke, macht es viel mehr Sinn. Der Compiler könnte einfach einen Garbage Collector wie jeden anderen Teil der Standardbibliothek verknüpfen. Ich glaube, ein Teil meiner Verwirrung ist darauf zurückzuführen, dass ich einen Müllsammler nur als Teil einer Interpreter-Implementierung und nicht als eigenständiges Programm betrachtet habe.
Christian Dean
23

Wie würde das mit kompilierten Sprachen funktionieren?

Deine Formulierung ist falsch. Eine Programmiersprache ist eine Spezifikation, die in einem technischen Bericht geschrieben wurde (ein gutes Beispiel finden Sie in R5RS ). Eigentlich meinen Sie sind bis zu einem gewissen spezifischen Sprache Implementierung (die eine Software ist).

(Einige Programmiersprachen haben schlechte oder sogar fehlende Spezifikationen oder entsprechen nur einer Beispielimplementierung. Dennoch definiert eine Programmiersprache ein Verhalten - z. B. hat sie eine Syntax und eine Semantik -, ist kein Softwareprodukt, könnte es aber sein implementiert von einem Softwareprodukt; viele Programmiersprachen haben mehrere Implementierungen; insbesondere ist "kompiliert" ein Adjektiv, das für Implementierungen gilt - auch wenn einige Programmiersprachen von Interpreten einfacher implementiert werden als von Compilern.)

Ich verstehe, dass, sobald der Compiler den Quellcode in den Zielcode kompiliert hat - insbesondere nativen Maschinencode -, es getan ist. Seine Arbeit ist beendet.

Beachten Sie, dass Interpreter und Compiler eine lose Bedeutung haben und einige Sprachimplementierungen als beides angesehen werden können. Mit anderen Worten, dazwischen gibt es ein Kontinuum. Lesen Sie das neueste Dragon Book und denken Sie über Bytecode , JIT-Kompilierung und dynamisch emittierenden C-Code nach, der nach demselben Verfahren in ein "Plugin" kompiliert und dann mit dlopen (3) geöffnet wird (und auf aktuellen Computern ist dies schnell genug, um kompatibel zu sein) eine interaktive REPL , siehe dies )


Ich empfehle dringend, das GC-Handbuch zu lesen . Zur Beantwortung wird ein ganzes Buch benötigt . Lesen Sie vorher die Garbage Collection- Wikipage (von der ich annehme, dass Sie sie gelesen haben, bevor Sie sie weiter unten lesen).

Das Laufzeitsystem der kompilierten Sprache Implementierung enthält die Speicherbereinigungseinrichtung , und die Compiler Code zu erzeugen , das ist fit zu diesem bestimmten Laufzeitsystem. Insbesondere Zuordnungsprimitive (werden zu Maschinencode kompiliert), die das Laufzeitsystem aufrufen (oder möglicherweise aufrufen).

Also, wie könnte das kompilierte Programm auch als Müll eingesammelt werden?

Nur durch die Ausgabe von Maschinencode, der das Laufzeitsystem verwendet (und "freundlich" und "kompatibel mit" ist).

Beachten Sie, dass Sie mehrere Garbage Collection-Bibliotheken finden, insbesondere Boehm GC , Ravenbrooks MPS oder sogar meinen (nicht verwalteten) Qish . Das Codieren eines einfachen GC ist nicht sehr schwierig (das Debuggen ist jedoch schwieriger und das Codieren eines kompetitiven GC ist schwierig ).

In einigen Fällen verwendete der Compiler einen konservativen GC (wie Boehm GC ). Dann gibt es nicht viel zu codieren. Der konservative GC scannt (wenn der Compiler seine Zuweisungsroutine oder die gesamte GC-Routine aufruft) manchmal den gesamten Aufrufstapel und geht davon aus, dass eine vom Aufrufstapel (indirekt) erreichbare Speicherzone aktiv ist. Dies wird als konservativer GC bezeichnet, da Tippinformationen verloren gehen: Wenn eine Ganzzahl auf dem Aufrufstapel wie eine Adresse aussieht, wird sie befolgt, usw.

In anderen (schwierigeren) Fällen bietet die Laufzeitumgebung eine Garbage Collection zum Kopieren von Daten (ein typisches Beispiel ist der Ocaml-Compiler, der Ocaml-Code mit einem solchen GC zu Maschinencode kompiliert). Dann geht es darum, auf den Aufrufstapeln genau alle Zeiger zu finden, von denen einige vom GC verschoben werden. Anschließend generiert der Compiler Metadaten, die Call-Stack-Frames beschreiben, die von der Laufzeit verwendet werden. So ist die Aufrufkonventionen und ABI immer spezifisch auf diese Implementierung (dh Compiler) und Laufzeitsystem.

In einigen Fällen handelt es sich bei dem vom Compiler generierten Maschinencode (tatsächlich sogar bei darauf verweisenden Abschlüssen ) um selbst gesammelten Müll . Dies gilt insbesondere für SBCL (eine gute Common-Lisp-Implementierung), die für jede REPL- Interaktion Maschinencode generiert . Dies erfordert auch einige Metadaten, die den Code und die darin verwendeten Aufrufrahmen beschreiben.

Speichert der Compiler eine Kopie eines Garbage Collection-Programms und fügt sie in jede von ihm generierte ausführbare Datei ein?

Art-of. Das Laufzeitsystem kann jedoch eine gemeinsam genutzte Bibliothek usw. sein. Manchmal (unter Linux und mehreren anderen POSIX-Systemen) kann es sich sogar um einen Skriptinterpreter handeln, der z. B. mit einem Shebang an execve (2) übergeben wird . Oder ein ELF- Dolmetscher, siehe elf (5) und usw.PT_INTERP

Übrigens sind die meisten Compiler für Sprache mit Garbage Collection (und deren Laufzeitsystem) heute freie Software . Laden Sie den Quellcode herunter und studieren Sie ihn.

Basile Starynkevitch
quelle
5
Sie meinen, dass es viele Implementierungen von Programmiersprachen ohne explizite Spezifikation gibt. Ja, dem stimme ich zu. Mein Punkt ist jedoch, dass eine Programmiersprache keine Software ist (wie ein Compiler oder ein Interpreter). Es ist etwas, das eine Syntax und eine Semantik hat (vielleicht sind beide schlecht definiert).
Basile Starynkevitch
4
@KonradRudolph: Das hängt ganz von Ihrer Definition von "formal" und "Spezifikation" ab :-D Es gibt die Ruby-Programmiersprachenspezifikation ISO / IEC 30170: 2012 , die eine kleine Teilmenge der Schnittmenge von Ruby 1.8 und 1.9 angibt. Es gibt die Ruby Spec Suite , eine Reihe von Beispielen für Grenzfälle, die als eine Art "ausführbare Spezifikation" dienen. Dann The Ruby Programming Language von David Flanagan und Yukihiro Matsumoto .
Jörg W Mittag
4
Auch die Ruby-Dokumentation . Diskussionen zu Problemen im Ruby Issue Tracker . Diskussionen zu den Mailinglisten ruby-core (Englisch) und ruby-dev (Japanisch). Erwartungen der Community an den gesunden Menschenverstand (z. B. Array#[]ist O (1) Worst-Case, Hash#[]ist O (1) amortisiert Worst-Case). Und zu guter Letzt: Matzs Gehirn.
Jörg W Mittag
6
@KonradRudolph: Der Punkt ist: Selbst eine Sprache ohne formale Spezifikation und nur eine einzige Inplementierung kann noch in "die Sprache" (die abstrakten Regeln und Einschränkungen) und "die Implementierung" (die Code-Verarbeitungsprogramme gemäß diesen Regeln und Beschränkungen). Und die Implementierung führt immer noch zu einer Spezifikation, wenn auch einer trivialen, nämlich: "Was auch immer der Code tut, ist die Spezifikation". So wurden schließlich die ISO-Spezifikation RubySpec und die RDocs geschrieben: durch Herumspielen mit der MRT und / oder Reverse Engineering.
Jörg W Mittag
1
Ich bin froh, dass du Bohems Müllmann großgezogen hast. Ich würde die OP-Studie empfehlen, da sie ein hervorragendes Beispiel dafür ist, wie einfach die Garbage Collection sein kann, auch wenn sie an einen vorhandenen Compiler "angeschraubt" ist.
Cort Ammon
6

Es gibt bereits einige gute Antworten, aber ich möchte einige Missverständnisse hinter dieser Frage klären.

Es gibt keine "nativ kompilierte Sprache" an sich. Beispielsweise wurde derselbe Java-Code auf meinem alten Telefon (Java Dalvik) interpretiert (und zur Laufzeit teilweise just-in-time kompiliert) und auf meinem neuen Telefon (ART) (im Voraus) kompiliert.

Der Unterschied zwischen der Ausführung von Code nativ und interpretiert ist viel weniger streng als es scheint. Beide benötigen einige Laufzeitbibliotheken und ein Betriebssystem, um zu funktionieren (*). Der interpretierte Code benötigt einen Interpreter, aber der Interpreter ist nur ein Teil der Laufzeit. Aber auch dies ist nicht streng, da Sie den Interpreter durch einen (Just-in-Time-) Compiler ersetzen könnten. Für eine maximale Leistung möchten Sie möglicherweise beides (die Desktop-Java-Laufzeit enthält einen Interpreter und zwei Compiler).

Unabhängig davon, wie der Code ausgeführt wird, sollte er sich gleich verhalten. Das Zuweisen und Freigeben von Speicher ist eine Aufgabe für die Laufzeit (genau wie das Öffnen von Dateien, das Starten von Threads usw.). In deiner Sprache schreibst du einfach new X()oder gleich. Die Sprachspezifikation sagt, was passieren soll und die Laufzeit tut es.

Ein Teil des freien Speichers wird zugewiesen, der Konstruktor wird aufgerufen usw. Wenn nicht genügend Speicher vorhanden ist, wird der Garbage Collector aufgerufen. Da Sie sich bereits in der Laufzeit befinden, bei der es sich um einen nativen Code handelt, spielt die Existenz eines Interpreters keine Rolle.

Es gibt wirklich keine direkte Verbindung zwischen der Interpretation von Code und der Garbage Collection. Es ist nur so, dass Low-Level-Sprachen wie C für die Geschwindigkeit und feinkörnige Steuerung von allem ausgelegt sind, was nicht gut zu der Idee eines nicht-nativen Codes oder eines Garbage Collectors passt. Es gibt also nur eine Korrelation.

Dies traf in alten Zeiten zu, als der Java-Interpreter sehr langsam und der Garbage Collector eher ineffizient war. Heutzutage sind die Dinge anders und das Sprechen über eine interpretierte Sprache hat keinen Sinn mehr.


(*) Zumindest wenn es um allgemeinen Code geht, Bootloader und ähnliches weglassen.

maaartinus
quelle
Sowohl Ocaml als auch SBCL sind native Compiler. Es gibt also "nativ kompilierte Sprach" -Implementierungen.
Basile Starynkevitch
@BasileStarynkevitch WAT? Wie hängt die Benennung weniger bekannter Compiler mit meiner Antwort zusammen? Ist SBCL als Compiler für eine ursprünglich interpretierte Sprache nicht ein Argument für meine Behauptung, dass die Unterscheidung keinen Sinn macht?
Maaartinus
Common Lisp (oder eine andere Sprache) wird nicht interpretiert oder kompiliert. Es ist eine Programmiersprache (eine Spezifikation). Seine Implementierung kann ein Compiler oder ein Interpreter oder etwas dazwischen sein (z. B. ein Bytecode-Interpreter). SBCL ist eine interaktiv kompilierte Implementierung von Common Lisp. Ocaml ist auch eine Programmiersprache (mit einem Bytecode-Interpreter und einem nativen Compiler als Implementierungen).
Basile Starynkevitch
@BasileStarynkevitch Das behaupte ich. 1. Es gibt keine interpretierte oder kompilierte Sprache (obwohl C selten interpretiert wird und LISP früher selten kompiliert wurde, aber das ist eigentlich egal). 2. Es gibt interpretierte, kompilierte und gemischte Implementierungen für die meisten bekannten Sprachen, und es gibt keine Sprache, die eine Kompilierung oder Interpretation ausschließt.
Maaartinus
6
Ich denke, Ihre Argumentation macht sehr viel Sinn. Der entscheidende Punkt für grok ist, dass Sie immer ein "natives Programm" oder "nie" ausführen, wie auch immer Sie es sehen möchten. Unter Windows ist keine Exe per se ausführbar. Es benötigt einen Loader und andere OS-Features, um zu starten und wird teilweise auch "interpretiert". Dies wird bei ausführbaren .net-Dateien deutlicher. java myprogist so viel oder so wenig nativ wie grep myname /etc/passwdoder ld.so myprog: Es ist eine ausführbare Datei (was auch immer das bedeutet), die ein Argument entgegennimmt und Operationen mit den Daten ausführt.
Peter A. Schneider
3

Die Details variieren zwischen den Implementierungen, im Allgemeinen ist dies jedoch eine Kombination der folgenden:

  • Eine Laufzeitbibliothek, die einen GC enthält. Dies übernimmt die Speicherzuweisung und hat einige andere Einstiegspunkte, einschließlich einer "GC_now" -Funktion.
  • Der Compiler erstellt Tabellen für den GC, damit er weiß, auf welche Felder in welchen Datentypen verwiesen wird. Dies wird auch für die Stack-Frames für jede Funktion durchgeführt, damit der GC den Stack nachverfolgen kann.
  • Wenn der GC inkrementell ist (GC-Aktivität ist mit dem Programm verschachtelt) oder gleichzeitig ausgeführt wird (in einem separaten Thread ausgeführt wird), enthält der Compiler auch speziellen Objektcode, um die GC-Datenstrukturen zu aktualisieren, wenn Verweise aktualisiert werden. Die beiden haben ähnliche Probleme bei der Datenkonsistenz.

Bei der inkrementellen und gleichzeitigen GC müssen der kompilierte Code und der GC zusammenarbeiten, um einige Invarianten beizubehalten. In einem Kopiersammler kopiert der GC beispielsweise Live-Daten von Platz A nach Platz B und hinterlässt dabei den Müll. Für den nächsten Zyklus werden A und B umgedreht und wiederholt. Eine Regel kann daher sein, dass jedes Mal, wenn das Benutzerprogramm versucht, auf ein Objekt in Raum A zu verweisen, dies erkannt wird und das Objekt sofort in Raum B kopiert wird, wo das Programm weiterhin darauf zugreifen kann. Eine Weiterleitungsadresse verbleibt im Bereich A, um dem GC anzuzeigen, dass dies geschehen ist, damit alle anderen Verweise auf das Objekt aktualisiert werden, wenn sie verfolgt werden. Dies wird als "Lesesperre" bezeichnet.

GC-Algorithmen wurden seit den 60er Jahren untersucht und es gibt umfangreiche Literatur zu diesem Thema. Google, wenn Sie weitere Informationen wünschen.

Paul Johnson
quelle