Skip to main content

Zusammenfassung

Am Anfang dieses Buches haben wir uns mit Prozessen befaßt und sie mit Hilfe von Lisp-Prozeduren beschrieben. Diese Prozeduren haben wir mit einer Reihe von Auswertungsmodellen erläutert: dem Substitutionsmodell in Kapitel 1, dem Umgebungsmodell in Kapitel 3 und dem metazirkulären Evaluator in Kapitel 4. Insbesondere durch die Beschäftigung mit dem metazirkulären Evaluator wurde das Geheimnis weitgehend gelüftet, das die Interpretation von lisp-ähnlichen Sprachen umgab. Aber auch der metazirkuläre Evaluator läßt wichtige Fragen unbeantwortet, weil mit ihm die Kontrollmechanismen in einem Lisp-System nicht erhellt werden können. Mit dem Evaluator läßt sich zum Beispiel nicht erklären, wie nach der Auswertung eines Teilausdrucks ein Wert an den Ausdruck geliefert werden kann, in dem dieser Wert dann verwendet wird; noch läßt sich mit dem Evaluator erklären, warum manche Prozeduren iterative Prozesse erzeugen (das heißt, mit konstantem Speicherbedarf ausgewertet werden), während andere Prozeduren rekursive Prozesse erzeugen. Diese Fragen bleiben unbeantwortet, weil der metazirkuläre Evaluator selbst ein Lisp-Programm ist und daher die Kontrollstruktur des zugrundeliegenden Lisp-Systems übernimmt. Um eine vollständigere Beschreibung der Kontrollstruktur des Lisp-Evaluators zu erhalten, müssen wir uns auf eine elementarere Ebene begeben als Lisp.

Mein Ziel hiebei ist es zu zeigen, daß die himmlische Maschine nicht eine Art göttlichen Lebewesens ist, sondern gleichsam ein Uhrwerk (wer glaubt, daß die Uhr beseelt ist, der überträgt die Ehre des Meisters auf das Werk), insofern darin nahezu alle die mannigfaltigen Bewegungen von einer einzigen ganz einfachen magnetischen Kraft besorgt werden, wie bei einem Uhrwerk alle die Bewegungen von dem so einfachen Gewicht.

Johannes Kepler (Brief an Herwart von Hohenburg, 1605)

This is a preview of subscription content, log in via an institution to check access.

Access this chapter

Chapter
USD 29.95
Price excludes VAT (USA)
  • Available as PDF
  • Read on any device
  • Instant download
  • Own it forever
eBook
USD 54.99
Price excludes VAT (USA)
  • Available as PDF
  • Read on any device
  • Instant download
  • Own it forever

Tax calculation will be finalised at checkout

Purchases are for personal use only

Institutional subscriptions

Preview

Unable to display preview. Download preview PDF.

Unable to display preview. Download preview PDF.

Referenzen

  1. In der ggT-Maschine gibt es keine Konstanten, aber wir werden diese in späteren Beispielen zu sehen bekommen. Wir werden eine Konstante in einem Datenpfaddiagramm durch ein Dreieck darstellen, in dem sich die Konstante befindet.

    Google Scholar 

  2. Im Kontext des Entwurfs von Maschinen werden Folgen von Steueranweisungen oft als Mikroprogramnme bezeichnet.

    Google Scholar 

  3. Um die Verwendung einer Konstanten anzugeben, schreiben Sie einfach die Konstante. Zum Beispiel steht (assign t 0) für die Anweisung, die 0 in Register t speichert

    Google Scholar 

  4. Diese Annahme vertuscht ein beträchtliches Maß an Komplexität. Normalerweise ist ein großer Teil der Implementierung eines Lisp-Systems der Aufgabe gewidmet, read und print funktionsfähig zu machen.

    Google Scholar 

  5. Man könnte argumentieren, daß wir das alte n nicht zu retten brauchten. Nachdem wir es dekrementiert und das Unterproblem gelöst haben, könnten wir es einfach wieder inkrementieren, um den alten Wert wiederherzustellen. Auch wenn diese Strategie für Fakultäten funktioniert, kann sie nicht im allgemeinen funktionieren, da der alte Wert eines Registers nicht immer aus dem neuen berechnet werden kann.

    Google Scholar 

  6. Wir können einen Keller als abstrakte Datenstruktur definieren, wobei wir festlegen, daß retten und wiederherstellen folgende Bedingungen erfüllen: Wenn der Keller im Zustand s 1 ist und Register x den Inhalt c hat, dann versetzt die Ausführung von (retten x) den Keller in den Zustand s 2. Wenn der Keller im Zustand s 2 ist und wir (wiederherstellen x) ausführen, wird der Inhalt von Register x wieder c und der Keller kehrt in den Zustand s 1 zurück. In Abschnitt 5.4 werden wir sehen, wie sich ein Keller mit Hilfe von elementareren Operationen implementieren läßt.

    Google Scholar 

  7. In unserem Programm erfolgt die Zuteilung über eine Folge von Verzweigungsanweisungen. Sie könnte auch alternativ in datengesteuertem Stil geschrieben werden (und in einem wirklichen System würde sie das wahrscheinlich), um nicht eine Abfrage nach der anderen durchführen zu müssen, und um die Definition neuer Ausdruckstypen zu er- leichtern. Eine Maschine, die speziell für Lisp entworfen wird, würde wahrscheinlich eine Anweisung zuteilen-nach-typ enthalten, die solche datengesteuerten Zuteilungen effizient durchführt.

    Google Scholar 

  8. Wir erweitern die Sammlung von Syntaxprozeduren aus Abschnitt 4.1.2 um das Prädikat ohne-arg? und ändern außerdem das Prädikat anwendung? derart, daß es das Vorhandensein von Argumenten abprüft: (define (ohne-arg? ausdr) (if (atom? ausdr) nil (null? (cdr ausdr)))) (define (anwendung? ausdr) (if (atom? ausdr) nil (not (null? (cdr ausdr)))))

    Google Scholar 

  9. Das ist eine wichtige, aber trickreiche Angelegenheit bei der Übersetzung von Algorithmen aus einer prozeduralen Sprache wie Lisp in eine Sprache für Registermaschinen. Statt nur das zu retten, was gebraucht wird, könnten wir auch vor jedem rekursiven Aufruf alle Register (außer wert) retten. Bei dieser Vorgehensweise spricht man von einem gerahmnten Keller. Das entsprechende Programm würde funktionieren, aber es würde vielleicht mehr Register retten als nötig; das könnte ein wichtiger Gesichtspunkt sein, wenn in einem System Kelleroperationen teuer sind.

    Google Scholar 

  10. Damit der Evaluator den letzten Operanden in einer Kombination abfragen kann, erweitern wir die in Abschnitt 4.1.2 angegebene Sammlung um folgende Syntaxprozedur: (define (letzter-operand? args) (null? (cdr args))) Wir können die Auswertungsschleife noch etwas effizienter machen, wenn wir die Auswertung des ersten Operanden ebenfalls zu einem Sonderfall machen; wir können die Initialisierung von argl zurückstellen, bis der erste Operand ausgewertet ist, um in diesem Fall argl nicht retten zu müssen. Der Übersetzer in Abschnitt 5.3 enthält diese und andere Optimierungen.

    Google Scholar 

  11. Die Reihenfolge der Auswertung der Operanden ist bei dem metazirkulären Evaluator durch die Reihenfolge der Auswertung der Argumente des cons in der else-Klausel der Prozedur liste-der-werte (Abschnitt 4.1.1) festgelegt.

    Google Scholar 

  12. Der metazirkuläre Evaluator kehrte die Reihenfolge der Argumente nicht um. Die Reihenfolge ist hier deshalb anders, weil wir den in Abschnitt 4.1.1 angegebenen rekursiven Prozeß der Argumentakkumulation in einen iterativen Prozeß umgewandelt haben. Wir hängen jedes Argument mit cons an die anderen an, sobald wir es ausgewertet haben, statt es zu retten, bis die übrigen Argumente akkumuliert sind.

    Google Scholar 

  13. Wir können die Prozedur (define (anwenden-elementare-prozedur p args) (apply (eval (elementare-id p) benutzer-anfangs-umgebung) (umkehren args))) verwenden, wobei apply und eval die elementaren Prozeduren apply und eval in dem zugrundeliegenden Lisp sind. (Die Argumente müssen umgekehrt werden, um der Tatsache Rechnung zu tragen, daß argl die Argumente in umgekehrter Reihenfolge auflistet.) Als Alternative können wir die in Abschnitt 4.1.4 angegebene Definition von anwenden-elementare-prozedur verwenden, entsprechend modifiziert, um der umgekehrten Reihenfolge der Argumente Rechnung zu tragen.

    Google Scholar 

  14. Konstr-bindungen könnte erweitern-umgebung direkt aufrufen, statt über die zusätzliche Prozedur erweitern-bindungs-umgebung. Wir haben sie so definiert, weil wir erweitern-bindungs-umgebung für den Übersetzer in Abschnitt 5.3 brauchen.

    Google Scholar 

  15. Wir haben in Abschnitt 5.1 gesehen, wie solch ein Prozeß mit einer Registerrnaschine implementiert werden kann, die keinen Keller hat; der Zustand des Prozesses wurde in einer festen Anzahl von Registern gespeichert.

    Google Scholar 

  16. Diese Implementierung der Endrekursion in auswerten-sequenz ist eine Variante einer wohlbekannten Optimierungstechnik, mit der viele Übersetzer arbeiten. Bei der Übersetzung einer Prozedur, deren letzte Anweisung ein weiterer Prozeduraufruf ist, kann man den zweiten Prozeduraufruf durch einen Sprung auf die Einsprungstelle der zweiten Prozedur ersetzen. Mit dem Einbau dieser Strategie in den Interpretierer, wie wir das in diesem Abschnitt getan haben, steht diese Optimierung einheitlich für die ganze Sprache zur Verfügung.

    Google Scholar 

  17. Wir können keine-weiteren-ausdr? wie folgt definieren: (define keine-weiteren-ausdr? null?)

    Google Scholar 

  18. Diese Implementierung von define läßt ein subtiles Problem bei der Behandlung interner Definitionen außer acht, wenn sie auch in den meisten Fällen korrekt funktioniert. Wir werden in Abschnitt 5.2.5 sehen, worin das Problem besteht und wie es sich lösen läßt.

    Google Scholar 

  19. Wir könnten diese Initialisierung auch nur nach dem Auftreten eines Fehlers durchführen, an der Stelle fehler-anzeigen, aber wenn wir es in der Treiberschleife tun, können wir damit auf einfache Weise die Leistung des Evaluators überwachen, wie wir weiter unten sehen werden.

    Google Scholar 

  20. Das ist der Grund für die Bemerkung ”Das Management ist dafür nicht verantwortlich” in Kapitel 1, Fußnote 23. Indem jemand bei der Implementierung einer Sprache darauf besteht, daß die Definitionen interner Prozeduren am Anfang stehen, behält er sich das Recht vor, den tatsächlich verwendeten Mechanismus für die Auswertung dieser Definitionen offen zu lassen. Die Wahl der einen oder einer anderen Auswertungsregel mag hier als ein geringes Problem erscheinen, das nur die Interpretation ”schlecht aufgebauter” Programme betrifft. Wir werden jedoch in Abschnitt 5.3.7 sehen, daß wir mit der Übernahme eines Modells simultaner Gültigkeit für interne Definitionen einige häßliche Schwierigkeiten vermeiden können, die sonst bei der Implementierung eines Übersetzers auftreten würden.

    Google Scholar 

  21. Diejenigen, die Scheme am MIT implementiert haben, stützen Alyssa aus folgendem Grund: Eva hat im Prinzip recht — die Definitionen sollten als simultan angesehen werden. Aber es scheint schwierig zu sein, einen allgemeingültigen effizienten Mechanismus zu implementieren, der tut, was Eva fordert. Ohne einen solchen Mechanismus ist es besser, in den schwierigen Fällen simultaner Definitionen einen Fehler anzuzeigen (Alyssas Auffassung) als eine falsche Antwort zu liefern (wie Ben es gern hätte).

    Google Scholar 

  22. Man achte bei der Untersuchung des Übersetzers darauf, die ÜbersetzungszeitUmgebung (die in den Prozeduren mit ue-z-umg angegeben ist) nicht mit dem Register umg der Maschine zu verwechseln, auf der der übersetzte Code laufen soll (das manchmal als Symbol in den Anweisungen auftritt, die der Übersetzer erzeugt). Man verwechsle auch nicht den Fortsetzungsdeskriptor (der im Übersetzer gewöhnlich mit forts angegeben ist) mit dem Maschinenregister weiter. Außerdem beziehen sich die Prozeduren des Übersetzers auf den zu übersetzenden Ausdruck mit ausdr. Das sollte nicht zu einer Verwechslung mit dem Register ausdr der Interpretierermaschine führen, denn übersetzter Code nimmt überhaupt niemals Bezug auf das Register ausdr. Der übersetzte Code bekommt einen auszuwertenden Ausdruck gar nicht zu sehen; er verkörpert die Auswertung eines bestimmten Ausdrucks. Aus demselben Grund wird in übersetztem Code auch das Register unausgew nicht verwendet.

    Google Scholar 

  23. Man bemerke den Gegensatz zum Interpretierer. Im Interpretierer würde umg unbedingt gerettet, bevor der Operator ausgewertet wird. Im Übersetzer wird bewahren verwendet, so daß umg nur gerettet wird, wenn das wirklich nötig ist.

    Google Scholar 

  24. Im gesamten Übersetzer bedeutet das Ziel nil, daß uns der gelieferte Wert nicht interessiert. Der elementare Codegenerator konstr-register-zuweisung (Abschnitt 5.3.4) vermeidet die Generierung einer assign-Anweisung, wenn das Ziel nil ist.

    Google Scholar 

  25. Definitionen unterscheiden sich von Zuweisungen auch darin, daß sie neue Bindungen erzeugen statt vorhandene Bindungen zu ändern. Man erinnere sich, daß wir eine Übersetzungszeit-Umgebung mitführen, die die Struktur der Laufzeit-Umgebung widerspiegeln soll, die bei der Ausführung des übersetzten Codes gilt. Da bei der Ausführung der übersetzten Definition eine Bindung zu der Laufzeit-Umgebung hinzugefügt wird, können wir auch die Erweiterung der Übersetzungszeit-Umgebung zum Zeitpunkt der Übersetzung einer Definition in Erwägung ziehen, um Anderungen der Laufzeit-Umgebung widerzuspiegeln. Es ist zwar möglich, eine solche Strategie zu verfolgen, es ergeben sich jedoch komplexe Probleme bei dem Versuch, zum Zeitpunkt der Übersetzung Veränderungen der Umgebung zu berücksichtigen, die zur Laufzeit auftreten könnten. Siehe Abschnitt 5.3.7, wo die Übersetzungszeit-Umgebung und die durch define aufgeworfenen Probleme erörtert werden.

    Google Scholar 

  26. Um die relevanten Teile aus dem lambda-Ausdruck herauszulösen, brauchen wir zwei neue Syntaxprozeduren zusätzlich zu den im Interpretierer (Abschnitt 4.1.2) verwendeten: (define (lambda-parameter ausdr) (cadr ausdr)) (define (lambda-rumpf ausdr) (cddr ausdr))

    Google Scholar 

  27. Wenn wir stattdessen schreiben würden (define (append-anwe isungs-f olgen seqs) ‹Rumpf wie oben›) dann müßten die Argumente jedesmal vor einem Aufruf von append-anweisungs-folgen zu einer Liste kombiniert werden.

    Google Scholar 

  28. Konstr-menge ist eine Gleichheitsoperation, da wir Mengen als ungeordnete Listen darstellen. Wir führen Sie hier explizit an, um die Abstraktionsbarriere um die Mengendarstellung aufrecht zu erhalten.

    Google Scholar 

  29. Um den Einsatz des quote an dieser Stelle zu verstehen, betrachte man die Konstruktion der Anweisung (assign wert ’apfel), was eigentlich die Liste (assign wert (quote apfel)) ist. Das Anweisungsfragment (quote apfel) wird durch Auswertung des Ausdrucks (konstr-konstante ’apfel) erzeugt, wobei konstr-konstante auf das Symbol apf el angewendet wird. Wenn der Rumpf von konstr-konst ant e einfach (konstr-wert-spez leere-menge k) wäre, dann würde die erzeugte Anweisung (assign wert apfel) lauten, und das ist nicht das, was wir wollten.

    Google Scholar 

  30. In Lisp-Dialekten steht diese Möglichkeit standardmäßig zur Verfügung, entweder elementar oder als Prozedur, die mit Hilfe von elementaren Operationen zur Zeichenbearbeitung geschrieben wurde. Wenn wir davon sprechen, daß die Prozedur ein ”neues” Symbol erzeugt, meinen wir damit, daß das erzeugte Symbol mit Sicherheit nicht eq? irgendein vorhandenes Symbol ist. In der Implementierung von Scheme am MIT ist konstr-neues-symbol äquivalent mit einer elementaren Prozedur generate-uninterned-symbol: (define konstr-neues-symbol generate-uninterned-symbol) Die Bezeichnung ”uninterned” bezieht sich auf die Operation intern machen, mit der in Lisp eingegebene Zeichenketten in (Zeiger auf) Symbole umgewandelt werden. Siehe Abschnitt 5.4.1.

    Google Scholar 

  31. Beim Übersetzen von Zugriffen auf, Zuweisungen an und Definitionen von Variablen haben wir die Übersetzungszeit-Umgebung an die elementaren Codegeneratoren weitergegeben; wir verwenden diese Umgebung jedoch nicht. In Abschnitt 5.3.7 erörtern wir, wie diese Codegeneratoren zu modifizieren sind, damit der Übersetzer die ÜbersetzungszeitUmgebung nutzt.

    Google Scholar 

  32. Um den Übersetzer wirklich laufen lassen zu können, müssen wir erwe it ere-ue-z-umg definieren. Diese Definition wird in Abschnitt 5.3.7 angeführt. Da wir im Moment die Übersetzungszeit-Umgebung nicht verwenden, können wir vorläufig eine Ersatzprozedur definieren, die einfach die leere Liste liefert.

    Google Scholar 

  33. Wenn wir in Abschnitt 5.3.7 den Übersetzer um die lexikalische Adressierung erweitern, müssen interne Definitionen wie iter herausgesucht und entfernt werden wie in Abschnitt 5.2.5 erläutert. Da wir im Moment die Übersetzungszeit-Umgebung nicht verwenden, verhält sich der Übersetzer vorläufig noch richtig, wenn er die interne Definition von iter als gewöhnliche Definition behandelt und dafür uebersetze-definition verwendet.

    Google Scholar 

  34. Wir haben hier mit demselben Symbol + sowohl den Operator der Quellsprache als auch die Maschinenoperation bezeichnet. Im allgemeinen wird es keine direkte Entsprechung zwischen den elementaren Operationen der Quellsprache und den elementaren Operationen der Maschine geben.

    Google Scholar 

  35. Für die elementaren Operationen reservierte Begriffe zu verwenden ist im allgemeinen keine gute Idee, da dann ein Benutzer diese Namen nicht an andere Prozeduren binden kann. Wenn wir den Übersetzer um einen reservierten Namen erweitern, der bereits in Gebrauch ist, werden außerdem vorhandene Programme, in denen Prozeduren mit diesen Namen definiert sind, nicht mehr funktionieren. In Übung 5.40 finden sich Ideen, wie sich dieses Problem vermeiden läßt.

    Google Scholar 

  36. Da eine übersetzte Prozedur ein Objekt ist, das das System eventuell auszudrucken versucht, ändern wir auch die Systemoperation benutzer-print (aus Abschnitt 4.1.4) derart, daß sie nicht versucht, die Komponenten einer übersetzten Prozedur auszudrucken: (define (benutzer-print objekt) (cond ((zusammengesetzte-prozedur? objekt) (print (list ’zusammengesetzte-prozedur (parameter objekt) (prozedur-rumpf objekt) ’[prozedur-umg]))) ((uebersetzte-prozedur? objekt) ;neue Klausel (print ’[uebersetzte-prozedur])) (else (print objekt))))

    Google Scholar 

  37. Vorläufig verwenden wir die Übersetzungszeit-Umgebung nicht, wir können daher einen beliebigen Wert (zum Beispiel die leere Liste) für anfangs-ue-z-umg angeben.

    Google Scholar 

  38. Das gilt nicht, wenn wir inkrementelle interne Definitionen, interaktive (Neu-) Definitionen und relative Auswertung von Definitionen zulassen. Wir verbieten daher def ine, bis wir es weiter unten gesondert behandelt haben.

    Google Scholar 

  39. Diese Änderung beim Nachsehen von Variablen ist erforderlich, wenn wir die Suchmethode zum Entfernen interner Definitionen implementieren wollen. (Vergleiche Übung 5.24.) Wie wir weiter unten sehen werden, müssen wir diese Definitionen entfernen, damit die lexikalische Adressierung funktioniert.

    Google Scholar 

  40. Der Programmiertrick in diesem Beispiel zeigt, wie man rekursive Prozeduren auswerten kann, ohne auf def ine zurückzugreifen. Der berühmteste Trick dieser Art ist der Y- Operator, mit dem die Rekursion ”in reinem λ-Kalkül” implementiert werden kann. Einzelheiten siehe Stoy 1977.

    Google Scholar 

  41. Die Implementierung von Scheme am MIT beinhaltet einen Algorithmus zur lexikalischen Adressierung, der indirekt-define genauso meistert wie andere Verwendungen von define, zum Beispiel vom Benutzer interaktiv in andere als die globale Umgebung eingegebene Definitionen. Dieser Algorithmus zur lexikalischen Adressierung ist ein sehr viel komplexerer Mechanismus als der von uns in diesem Abschnitt erörterte. Ihm liegt die Idee zugrunde, die lexikalische Adressierung mit zur Laufzeit erzeugten ”Verletzungsmeldungen” zu verbinden, die den Interpretierer warnen, daß gewisse lexikalische Adressen nicht mehr gültig sein könnten und neu berechnet werden müssen.

    Google Scholar 

  42. Automagisch: ”Automatisch, aber in einer Weise, die der Sprecher aus irgendeinem Grund (üblicherweise, weil sie zu kompliziert, zu häßlich oder vielleicht sogar zu trivial ist) nicht näher erklären möchte.” (Steele 1983)

    Google Scholar 

  43. Wir könnten Vektoren als Listen von Elementen darstellen. Die Zugriffszeit wäre dann jedoch nicht unabhängig von dem Index, da der Zugriff auf das nte Element einer Liste n — 1 cdr-Operationen erfordert.

    Google Scholar 

  44. Der Vollständigkeit halber sollten wir eine Operation konstr-vektor angeben, die Vektoren konstruiert. In der gegenwärtigen Anwendung werden wir jedoch mit den Vektoren nur Modelle von festgelegten Speicherabschnitten bilden.

    Google Scholar 

  45. Das ist genau dieselbe Idee von ”typisierten Daten”, wie wir sie in Kapitel 2 für den Umgang mit generischen Operatoren eingeführt haben. Hier werden die Datentypen jedoch auf der elementaren Maschinenebene miteinbezogen und nicht erst bei der Verwendung von Listen konstruiert.

    Google Scholar 

  46. Typinformation kann auf verschiedene Weise codiert werden, das hängt von den Details der Maschine ab, auf der das Lisp-System implementiert werden soll. Die Effizienz bei der Ausführung von Lisp-Programmen hängt stark von der Wahl dieser Codierung ab, aber es ist schwierig, allgemeine Regeln für eine gute Wahl zu formulieren. Die direkteste Art der Implementierung von typisierten Zeigern ist die Zuteilung einer festen Menge von Bits in jedem Zeiger als Typenfeld, in dem der Datentyp codiert ist. Wichtige Fragen beim Entwurf einer solchen Darstellung sind: Wieviele Typenbits werden benötigt? Wie groß müssen die Vektorindizes sein? Wie effizient können die elementaren Maschinenanweisungen zur Bearbeitung des Typenfelds der Zeiger eingesetzt werden? Maschinen mit einer speziellen Hardware für die effiziente Behandlung von Typenfeldern heißen Maschinen mit tagged architecture (”gekennzeichneter Architektur”).

    Google Scholar 

  47. Mit dieser Entscheidung über die Darstellung von Zahlen wird festgelegt, ob mit eq?, mit dem Zeiger auf Gleichheit abgefragt werden, auch die Gleichheit von Zahlen abgefragt werden kann. Wenn der Zeiger die Zahl selbst enthält, dann haben gleiche Zahlen denselben Zeiger. Aber wenn der Zeiger den Index eines Speicherplatzes enthält, an dem die Zahl gespeichert ist, haben dieselben Zahlen nur dann garantiert denselben Zeiger, wenn wir darauf achten, daß niemals dieselbe Zahl an mehr als einem Speicherplatz gespeichert wird.

    Google Scholar 

  48. Das erfolgt auf die gleiche Weise wie eine Zahl als Folge von Ziffern geschrieben wird, wobei in diesem Fall jede ”Ziffer” eine Zahl zwischen 0 und der größten Zahl ist, die in einem einzelnen Zeiger gespeichert werden kann.

    Google Scholar 

  49. Freier Speicher kann auch auf andere Weise gefunden werden. Wir könnten zum Beispiel alle unbenutzten Paare in einer Freiliste zusammenbinden. Die freien Speicherplätze stehen hintereinander (es kann daher durch Hochzählen eines Zeigers auf sie zugegriffen werden), weil wir mit einer verdichtenden Speicherbereinigung arbeiten, wie wir in Abschnitt 5.4.2 sehen werden.

    Google Scholar 

  50. Das ist im wesentlichen die Implementierung von cons mit set-car ! und set-cdr!, wie sie in Abschnitt 3.3.1 beschrieben wurde. Die Operation get-neues-paar in jener Implementierung wird hier mit dem frei-Zeiger realisiert.

    Google Scholar 

  51. Das ist vielleicht eines Tages nicht mehr der Fall, wenn Speicher so groß werden, daß der freie Speicherplatz während der Lebensdauer des Computers nicht mehr ausgehen kann. Ein Jahr hat etwa 3 × 1013 Mikrosekunden, wenn wir also einmal pro Mikrosekunde eine cons-Operation durchführten, bräuchten wir etwa 1015 Speicherzellen, um eine Maschine zu bauen, die 30 Jahre arbeitet, ohne daß ihr der Speicherplatz ausgeht. Soviel Speicherplatz scheint nach heutigen Standards absurd, aber es ist physikalisch nicht unmöglich. Andererseits werden die Prozessoren schneller und ein zukünftiger Computer könnte eine große Zahl von Prozessoren haben, die einen einzigen Speicher parallel bearbeiten, so daß der Speicherplatz vielleicht sehr viel schneller aufgebraucht wird als wir hier annahmen.

    Google Scholar 

  52. Wir gehen hier davon aus, daß der Keller als Liste dargestellt ist wie in Abschnitt 5.4.1 gezeigt, so daß auf die Elemente im Keller über den Zeiger im Kellerregister zugegriffen werden kann.

    Google Scholar 

  53. Diese Vorgehensweise hat Minsky erfunden und als Teil der Implementierung von Lisp auf der PDP-1 am MIT-Forschungsinstitut für Elektronik zum ersten Mal implementiert. Sie wurde von Fenichel und Yochelson (1969) weiterentwickelt, um sie für die Implementierung von Lisp im Multics time-sharing System einsetzen zu können. Später entwickelte Baker (1978) eine ”real-time”-Version der Methode, bei der die Verarbeitung von Lisp während der Speicherbereinigung nicht anhalten muß. Eine andere weitverbreitete Technik der Speicherbereinigung ist die Methode des Markierens und Auffegens (engl. marksweep). Sie besteht darin, alle von den Maschinenregistern aus erreichbaren Strukturen nachzuvollziehen und jedes angetroffene Paar zu markieren. Wir durchlaufen dann den gesamten Speicher und jede nicht markierte Speicherstelle wird als Abfall ”aufgefegt” und für die Wiederverwendung bereitgestellt. Eine vollständige Erörterung dieser Methode ist in Allen 1978 zu finden. Der Algorithmus von Minsky, Fenichel und Yochelson ist der überwiegend verwendete Algorithmus für Systeme mit großem Speicher, weil er nur den noch gebrauchten Teil des Speichers untersucht. Im Gegensatz dazu muß beim Markieren und Auffegen in der Phase des Auffegens der gesamte Speicher überprüft werden. Ein zweiter Vorteil beim Anhalten und Kopieren besteht darin, daß diese Art der Speicherbereinigung verdichtet. Das heißt, am Ende der Speicherbereinigung befinden sich die noch gebrauchten Daten auf hintereinanderstehenden Sp eicherplätzen, alle Abfallpaare wurden ”herausgedrückt”. Das kann äußerst wichtig für die Leistung von Maschinen mit virtuellem Speicher sein, bei denen für den Zugriff auf weit auseinander liegende Speicheradressen zusätzliche Paging-Operationen erforderlich sein können.

    Google Scholar 

  54. Die Register des Interpretierers umfassen nicht die Register des Systems für die Speicherzuteilung — ursprung, die-cars, die-cdrs und die anderen später in diesem Abschnitt eingeführten Register.

    Google Scholar 

  55. Der Ausdruck broken heart wurde von David Cressey geprägt, der eine Speicherbereinigung für MDL schrieb, einen Lisp-Dialekt, der in den frühen 70er Jahren am MIT entwickelt wurde.

    Google Scholar 

Download references

Author information

Authors and Affiliations

Authors

Rights and permissions

Reprints and permissions

Copyright information

© 1993 Springer-Verlag Berlin Heidelberg

About this chapter

Cite this chapter

Abelson, H., Sussman, G.J., Sussman, J. (1993). Rechnen mit Registermaschinen. In: Struktur und Interpretation von Computerprogrammen. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-662-01163-8_5

Download citation

  • DOI: https://doi.org/10.1007/978-3-662-01163-8_5

  • Publisher Name: Springer, Berlin, Heidelberg

  • Print ISBN: 978-3-540-56934-3

  • Online ISBN: 978-3-662-01163-8

  • eBook Packages: Springer Book Archive

Publish with us

Policies and ethics