Auf der abstrakten Ebene können Sie alles, was Sie wollen, in eine Sprache einfügen, die Sie entwerfen.
Auf der Implementierungsebene ist es unvermeidlich, dass einige dieser Dinge einfacher zu implementieren sind, einige kompliziert sind, einige schneller gemacht werden können, andere langsamer sein müssen und so weiter. Um dies zu berücksichtigen, müssen Designer häufig schwierige Entscheidungen treffen und Kompromisse eingehen.
Auf der Implementierungsebene besteht eine der schnellsten Möglichkeiten für den Zugriff auf eine Variable darin, ihre Adresse herauszufinden und den Inhalt dieser Adresse zu laden. In den meisten CPUs gibt es spezielle Anweisungen zum Laden von Daten von Adressen, und diese Anweisungen müssen normalerweise wissen, wie viele Bytes sie laden müssen (eins, zwei, vier, acht usw.) und wo sie die Daten ablegen müssen, die sie laden (einzelnes Register, Register) Paar, erweitertes Register, anderer Speicher usw.). Wenn der Compiler die Größe einer Variablen kennt, kann er genau wissen, welche Anweisung für die Verwendung dieser Variablen ausgegeben werden soll. Wenn der Compiler die Größe einer Variablen nicht kennt, müsste er auf etwas Komplizierteres und wahrscheinlich Langsameres zurückgreifen.
Auf der abstrakten Ebene besteht der Punkt der Untertypisierung darin, Instanzen eines Typs zu verwenden, bei denen ein gleicher oder allgemeinerer Typ erwartet wird. Mit anderen Worten, Code kann geschrieben werden, der ein Objekt eines bestimmten Typs oder etwas anderes erwartet, ohne vorher zu wissen, was genau dies sein würde. Und da mehr abgeleitete Typen mehr Datenelemente hinzufügen können, muss ein abgeleiteter Typ nicht unbedingt die gleichen Speicheranforderungen wie seine Basistypen haben.
Auf der Implementierungsebene gibt es keine einfache Möglichkeit für eine Variable einer vorgegebenen Größe, eine Instanz unbekannter Größe zu speichern und auf eine Weise zuzugreifen, die Sie normalerweise als effizient bezeichnen würden. Es gibt jedoch eine Möglichkeit, Dinge ein wenig zu verschieben und eine Variable zu verwenden, um das Objekt nicht zu speichern, sondern um das Objekt zu identifizieren und es an einem anderen Ort speichern zu lassen. Auf diese Weise entsteht eine Referenz (z. B. eine Speicheradresse) - eine zusätzliche Indirektionsebene, die sicherstellt, dass eine Variable nur Informationen mit fester Größe enthalten muss, solange wir das Objekt über diese Informationen finden können. Um dies zu erreichen, müssen wir nur die Adresse (feste Größe) laden und können dann wie gewohnt mit den Offsets des Objekts arbeiten, von denen wir wissen, dass sie gültig sind, auch wenn das Objekt mehr Daten in Offsets hat, die wir nicht kennen. Wir können das tun, weil wir nicht
Auf der abstrakten Ebene können Sie mit dieser Methode einen (Verweis auf einen) string
in einer object
Variablen speichern , ohne die Informationen zu verlieren, die ihn zu einem machen string
. Es ist in Ordnung für alle Typen, so zu arbeiten, und man könnte auch sagen, dass es in vielerlei Hinsicht elegant ist.
Auf der Implementierungsebene erfordert die zusätzliche Indirektionsebene jedoch mehr Anweisungen, und auf den meisten Architekturen wird jeder Zugriff auf das Objekt etwas langsamer. Sie können zulassen, dass der Compiler mehr Leistung aus einem Programm herausholt, wenn Sie einige häufig verwendete Typen in Ihre Sprache aufnehmen, die nicht über diese zusätzliche Indirektionsebene (die Referenz) verfügen. Durch Entfernen dieser Indirektionsebene kann der Compiler jedoch keine speichersichere Untertypisierung mehr zulassen. Dies liegt daran, dass, wenn Sie Ihrem Typ weitere Datenelemente hinzufügen und einem allgemeineren Typ zuweisen, alle zusätzlichen Datenelemente, die nicht in den für die Zielvariable zugewiesenen Speicherplatz passen, entfernt werden.