Übersetzung objektorientierter Sprachen André Christ Münster, 5. Januar 2007 Seminar Übersetzung von künstlichen Sprachen Gliederung 1. Objektorientierte Konzepte 2. Übersetzung 1. Klassen und Methoden 2. Vererbung 3. Parametrisierung 3. Zusammenfassung & Fazit 1 / 30 Objektorientierte Programmierung Dr. Alan Kay: „Objektorientierte Programmierung“ Austausch von Nachrichten zwischen Objekten Objekte Zustand: Instanzvariablen Verhalten: Methoden Identität: Bei Erzeugung (Instanziierung) festgelegt (new) Nachrichten Anfrage, eine Operation auf einem Objekt auszuführen Zur Laufzeit wird „passende“ Methode ausgewählt und ausgeführt Syntax z.B.: obj.m() oder obj->m() 1. Objektorientierte Konzepte 2 / 30 Klassen und Methoden Klasse Beschreibt Menge von Objekten gleicher Struktur (Methoden, Instanzvariablen) Klassendefinition: Instanzvariablen und Methoden Führt neuen Datentyp ein Methoden Mögliche Operationen eines Objekts Vergleichbar mit Funktionen (Prozeduren) aus imperativen Sprachen Aber: Können auf Instanzvariablen ihres Objekts zugreifen (this) public class IntStack { private int size; private int[] data; public int pop() { return this.data[this.size – 1]; this.size--; } // Instanzvariable // Instanzvariable // Methode } 1. Objektorientierte Konzepte 3 / 30 Vererbung Vererbung B erbt von A: Alle Instanzvariablen und Methoden einer Superklasse A in der Unterklasse B enthalten Spezialisierung Unterklasse kann Instanzvariablen und Methoden hinzufügen Implementierung geerbter Methoden änderbar (Signatur gleich Invariante Spezialisierung) Figur Flaeche() Kreis Flaeche() Radius() Einfachvererbung (1 Superklasse, V-Baum) Mehrfachvererbung (n Superklassen, V-Graph) Teiltypregel a,b Variablen der Klassen A, B Zuweisung a = b gültig, falls A und B identisch oder B Unterklasse (auch indirekt) von A Zugriff nur über in A definierte Schnittstelle (A-Sicht auf B) 1. Objektorientierte Konzepte 4 / 30 Polymorphie Polymorphie („Vielgestaltigkeit“) Variablen, Datenobjekte sowie Argument- und Rückgabewerte können mehr als einen Datentypen annehmen Prinzip nicht auf objektorientierte Sprachen beschränkt Polymorphie insbesondere auch bei funktionalen Sprachen Klassifikation Subklassen-Polymorphie Parametrische-Polymorphie Überladen Coercion Universelle-Polymorphie Ad hoc-Polymorphie Nach: Strachey / Cardelli, Wegner 1. Objektorientierte Konzepte 5 / 30 Subklassen-Polymorphie public class Kreis extends Figur {…} Figur Flaeche() Kreis k = new Kreis(); k.Flaeche(); // Kreis::Flaeche() Figur f = new Kreis(); f.Flaeche(); // Superklassenkontext // Kreis::Flaeche() Kreis Flaeche() Anwendungsfall: Plugins / Frameworks Methoden-Auswahlregel Ein Objekt einer Unterklasse B von A kann im Superklassenkontext von A verwendet werden Methode m wird in Unterklasse überschrieben Methode m muss auch dann ausgeführt werden, wenn das Objekt B in Variable vom Typ A vorliegt (A-Sicht auf B) Erst zur Laufzeit bestimmbar, welche Instanz die Verarbeitung einer Nachricht übernimmt 1. Objektorientierte Konzepte 6 / 30 Parametrische-Polymorphie Motivation: Gleiche Funktionalität für mehrere Datentypen notwendig (insb. Datenbehältern) Bisher (Unsicherer Cast / Typüberprüfung) Stack oldStack = new Stack(); oldStack.push(new Integer(2)); String top = (String) oldStack.pop(); if (oldStack.pop() instanceof String) // Unsafe Downcast // instanceof type check Generische Klasse mit formalem Parameter T public class Stack<T> { public void push(T element) {…} public T pop() {…} } Instanziierung mit aktuellem Parameter String: Stack<String> stringStack = new Stack<String>(); stringStack.push("Hello World"); String top = stringStack.pop(); 1. Objektorientierte Konzepte 7 / 30 Gliederung 1. Objektorientierte Konzepte 2. Übersetzung 1. Klassen und Methoden 2. Vererbung 3. Parametrisierung 3. Zusammenfassung & Fazit 8 / 30 Übersetzung Aufspaltung des Übersetzungprozesses Abstrakte Maschine: Zwischencode, an Quellsprache angepasst Reale Maschine: Maschinencode, durch Prozessorarchitektur bestimmt (Weit verbreitet: CISC und RISC) Abstrakte Maschine für objektorientierte Sprache Programmspeicher (Zwischencode) Befehlsinterpreter (Ausführung des Zwischencodes) Stack (Methodeninkarnationen in Frames) Heap (u.a. Instanzen von Klassen) Virtuelle Maschine für objektorientierte Sprache Ausführungsumgebung moderner obj. Sprachen C# / Java Ausprägung einer abstrakten Maschine Ausführung des Zwischencodes zur Laufzeit des Programms 2. Übersetzung 9 / 30 Abstrakte Maschine Klassen- 5 deskriptor Klassendeskriptor Befehlszähler ... Framepointer Methodenrumpf 6 Methodenrumpf 0 Objekt Instvar 0 Objekt 3 Instvar 1 ... Objekt Instvar n ... Register Hilfspeicher ... Main() Virtuelle 4 Methodentabelle Befehlsinterpreter Heap Frame Programmspeicher 2 Object 1 Frame Object 2 1 ... Object n Stack Verweis (Zeiger) Befehlszähler: Zeigt auf abzuarbeitenden Befehl in Methodenrumpf Framepointer: Verweist auf den Frame (lokale Variablen einer Methode) einer Methodeninkarnation (passend zu Befehlszähler) In Anlehung an: Bauer, Höllerer 1998 2. Übersetzung 10 / 30 Klassendeskriptor Methodentabelle: Indizierte Datenstruktur mit Methodenselektoren (Namen) Methodenselektor verweist auf entsprechenden Methodenrumpf im Methodenarray Detaillierung Klassendeskriptor: ClassFile { // Referenz auf Superklasse u2 super_class; Superklasse Methodentabelle # Methoden # Instanzvariablen Methodenarray Klassendeskriptor Auszug aus Java-class Datei: MethodenSelektor 1 MethodenSelektor 2 // Anzahl Instanzvariablen u2 fields_count; Methodenrumpf 1 Methodenrumpf 2 // Anzahl der Methoden u2 methods_count; ... ... Methodenrumpf n MethodenSelektor n Methodenarray Methodentabelle // Name u. Typ d. Instanzvar. field_info fields[field_count]; // Methodentabelle method_info methods[methods_count]; […] } In Anlehung an: Bauer, Höllerer 1998 2. Übersetzung // Klassen und Methoden 11 / 30 Übersetzung von Methoden (1) Methodenrumpf im Wesentlichen wie Funktions- oder Prozedurrümpfe imperativer Sprachen Variablen, Schleifen, Verzweigungen ... Objektorientierte Sprachkonstrukte (in Methoden) Senden einer Nachricht: Object.message() (auch O->m()) Zugriff auf Instanzvariable: Object.variable this Selbstreferenz: this.message() / Zugriff auf Superklasse: super.message() / 2. Übersetzung // Klassen und Methoden (auch O->v) (auch self) this.variable super (auch parent) super.variable 12 / 30 Übersetzung von Methoden (2) Realisierung der Selbstreferenz this Methode m einer Klasse K Übersetzt als Funktion: Nachricht m an Objekt o vom Typ K: Umgewandelt in Funktionsaufruf: <ret> m(<args>) <ret> Km(K this, <args>) <ret> o.m(<args>) <ret> Km(o, <args>) Abbildung auf Konzept von Funktionen / Prozeduren imp. Sprachen Methodennamen -> Funktionsnamen Problem: Globaler Namensraum von Funktionen Funktionen müssen sich in ihrem Namen unterscheiden Codierungsschema: _ZN#<Klasse>#<Methode>E<Typ>* (GNU G++ 3.0) Stack::push(int element) Stack::push(float element) Stack::push(float comp, float imag) 2. Übersetzung // Klassen und Methoden _ZN5Stack4pushEi _ZN5Stack4pushEf _ZN5Stack4pushEff 13 / 30 Methodenaufrufe Statisches Binden (imp. Prozedur- und Funktionsaufruf) Funktionsaufruf wird zur Übersetzungszeit der Definition der Funktion zugeordnet Nach Typüberprüfung von Argument- und Rückgabewerten legt Übersetzer relative Speicheradresse fest Dynamische Bindungsregel „Überschreibt eine Klasse B eine Methode ihrer Superklasse A und wird eine Nachricht m an ein Objekt geschickt, dessen Klassenzugehörigkeit zur Übersetzungszeit nicht bekannt ist, so muss die Methodenimplementierung zur Laufzeit an das Objekt gebunden werden.“ Bauer, Höllerer 1998 2. Übersetzung // Vererbung 14 / 30 ur Dynamisches Binden mittels vtable Virtuelle Methodentabelle (vtable) In C++ auch virtuelle Funktionstabelle Sog. virtuelle Methoden in Unterklassen überschreibbar Einträge in der vtable verweisen auf Methodenimplementationen Sichten: Offsets in der vtable (siehe geschweifte Klammern) vtable f : Figur 0:Figur::Flaeche() vtable 0:Kreis::Flaeche() 1:Kreis::Radius() virtual Flaeche() k : Kreis r : Rechteck Flaeche() Flaeche() Radius() Ecken() q : Quadrat Kante() vtable 0:Rechteck::Flaeche() Figur Rechteck 1:Rechteck::Ecken() vtable 0:Rechteck::Flaeche() 1:Rechteck::Ecken() Figur Rechteck Quadrat 2:Quadrat::Kante() 2. Übersetzung // Vererbung 15 / 30 Realisierung vtable in C++ Instanziierung Quadrat q = new Quadrat(); Objekt erhält Zeiger auf vtable seiner Klasse f : Figur virtual Flaeche() r : Rechteck Methodenaufruf q->Flaeche(); In der vtable wird Adresse der Funktionsimplementation nachgeschlagen Effiziente Implementation durch Funktionszeiger in C: Flaeche() Ecken() q : Quadrat Kante() vtable 0:Rechteck::Flaeche() 1:Rechteck::Ecken() vtable 0:Rechteck::Flaeche() 1:Rechteck::Ecken() 2:Quadrat::Kante() Standardisierte Indezierung der vtable Umwandlung der Methodenaufrufe: q->Flaeche() (*(q->vtable[0]))() 2. Übersetzung // Vererbung 16 / 30 Realisierung vtable in C++ Subklassen-Polymorphie Quadrat q = new Quadrat(); Rechteck f = (Rechteck) q; f->Flaeche(); Sicht über vtable des Objekts vom Typ Quadrat q : Quadrat Kante() vtable 0:Rechteck::Flaeche() 1:Rechteck::Ecken() Figur Rechteck Quadrat 2:Quadrat::Kante() 2. Übersetzung // Vererbung 17 / 30 Mehrfachvererbung Diamant Problem (Auszug) Wiederholte Beerbung: Figur und Linie erhalten Methoden und Instanzvariablen die sie an Rechteck weitervererben Uneindeutigkeit wegen doppelter Methodennamen und Instanzvariablen (Skalieren()) Lösungsansatz: Echte Mehrfachvererbung vermeiden Mehrfachvererbung nur mit Superklassen ohne Implementierungsteil (Java, C#) z.B. Java Interfaces: public class Rechteck implements Figur, Linie { [...] } 2. Übersetzung // Vererbung g : GUIObjekt int farbtiefe virtual Zeichne() f : Figur l : Linie virtual Flaeche() Int[][] Koordinaten virtual Skalieren() Zeichne() virtual Skalieren() r : Rechteck Flaeche() Parallelpfad Realisierung 18 / 30 Übersetzung von Parametrisierung Ursprung in funktionaler Sprache ML Viel Gesprächsstoff bzgl. Umsetzung von Parametrisierung in objektorientierten Sprachen In C++, Java und C# nachträglich hinzugefügt “Correction these early oversights in C++ was a long and painful process, creating years of havoc as compilers never quite supported the same language, books never quite gave accurate information, trainers never quite taught the right stuff, and programmers never quite knew what to think“ (Betrand Mayer 1998, Entwickler der Programmiersprache Eiffel) Unterschiedliche Strategien C++ Templates Java Generics (ab J2SE 5.0) C# Generische Klassen (ab .NET 2.0) 2. Übersetzung // Parametrisierung 19 / 30 Parametrisierung in C++ (1) Generische Klassen: C++ Templates Übersetzer expandiert Templates anhand aktueller Parameter Für jeden aktuellen Parameter eigene Klasse Daher: Kopierende (auch heterogene) Übersetzung Stack<int> void push(int item) Stack<T> int pop() void push(T item) T pop() Stack<float> void push(float item) float pop() 2. Übersetzung // Parametrisierung 20 / 30 Parametrisierung in C++ (2) Umsetzung der Methodenaufrufe durch C++ Compiler Stack<int> intStackA; Stack<int> intStackB; intStackA.push(1); intStackB.push(2); Stack<float> floatStack; floatStack.push(1); floatStack.pop(); 2. Übersetzung // Parametrisierung _ZN5StackIiE4pushEi _ZN5StackIfE4pushEf _ZN5StackIfE3popEv 21 / 30 Parametrisierung in C++ (3) Strategie Dem Übersetzungsprozess vorgeschaltete Expansion Vgl. mit Makro-Expansion durch Präprozessor in C Bewertung Performanz Laufzeit: Parametrisierung bringt keinen Overhead mit sich – da Abbildung auf bekannte Sprachkonstrukte Keine Integration in den Sprachkern – generische Klassen sind nicht Bestandteil des Typsystems Programmgröße wächst stark an (Redundanter Code) 2. Übersetzung // Parametrisierung 22 / 30 Parametrisierung in Java (1) Java Generics Hervorgegangen aus Pizza-Projekt (später GJ-Projekt) Anforderung: Auf unveränderter JVM lauffähig Seit J2SE 5.0 offizieller Bestandteil Erasure Java-Compiler überprüft Typen (aktuelle Parameter) Formale Parameter werden durch ihren Bound ersetzt und Typkonvertierungen eingefügt Ergebnis: Raw Type, frei von generischen Instuktionen Auch: Homogene Übersetzung Bound (Obere Grenze): Implizit Object: Explizit Number: 2. Übersetzung // Parametrisierung class Stack<T> {...} class Stack<T extends Number> {...} 23 / 30 Parametrisierung in Java (2) Beispiel Erasure Stack<T> mit Bound Object: Generische Klasse: Raw Type (nach Erasure): public class Stack<T> { public void push(T element){…} public T pop() {…} } public class Stack { public void push(Object element){…} public Object pop() {…} } Stack<String> st = new Stack<String>(); st.push("Hello World"); Stack st = new Stack(); st.push("Hello World"); String top = st.pop(); String top = (String) st.pop(); 2. Übersetzung // Parametrisierung 24 / 30 Parametrisierung in Java (3) Probleme (u.a.) Stack<String> strStack = new Stack<String>(); strStack.push("Test"); Object tmp = strStack; Stack<Integer> intStack = (Stack<Integer>) tmp; [...] Integer intVal = intStack.pop(); // Unchecked cast // without type // CastException // later in Code Bewertung Keine Anpassungen an JVM nötig (Prämisse an Pizza-Projekt) Overhead durch Typkonvertierungen Generische Typen existieren zur Laufzeit nicht mehr stack instanceof Stack<Integer> nicht möglich Primitive Typen (int, float) können keine aktuellen Parameter sein, da kein gemeinsamer Bound existiert 2. Übersetzung // Parametrisierung 25 / 30 Parametrisierung in C# (1) Kompatibilität generischer Klasseninstanzen Datenstrukturen und Algorithmen der aktuellen Parameter identisch Referenztypen zueinander kompatibel (32-bit Pointer) Primitiven Datentypen untereinander und zu Referenzzypen inkompatibel Kopie der virtuellen Methodentabelle für jede generische Klasseninstanz Kompatible Klasseninstanzen verweisen auf gemeinsamen Code s1: Stack<string> s2: Stack<string> s3 : Stack<object> s4 : Stack<int> vtable Pointer vtable Pointer vtable Pointer vtable Pointer Instvar 1 Instvar 1 Instvar 1 Instvar 1 ... ... ... ... Instvar n Instvar n Instvar n Instvar n push() push() push() pop() Code push() pop() pop() Code push() ... Code pop() ... ... Code pop() object int string vtable Stack<string> 2. Übersetzung // Parametrisierung vtable Stack<object> vtable Stack<int> 26 / 30 Parametrisierung in C# (2) Strategie Kombination der Vorteile der heterogenen Übersetzung (C++) und der homogenen Übersetzung (Java) Bauer, Höllerer: Echte generische Übersetzung Bewertung Völlständige Integration generischer Typen in den Sprachkern Typüberprüfungen auch zur Laufzeit möglich Wenig Overhead zur Laufzeit da Typkonvertierungen nicht nötig Zusätzliche Verwaltung von vtables (Aber: Effiziente Implementation mit vtable Dictionaries möglich) 2. Übersetzung // Parametrisierung 27 / 30 Gliederung 1. Objektorientierte Konzepte 2. Übersetzung 1. Klassen und Methoden 2. Vererbung 3. Parametrisierung 3. Zusammenfassung & Fazit 28 / 30 Zusammenfassung & Fazit Grundlagen objektorientierter Sprachen Klassen und Objekte Methoden und Nachrichten Polymorphie Übersetzung Abstrakte Maschine Klassen und Methoden Gemeinsamkeiten mit imp. Prozeduren Vererbung echte Mehrfachvererbung wird meist vermieden Strategien zur Realisierung von Parametrisierung Hintergrund: Diskussionen um C++ Templates und Java Generic Tieferes Verständnis für das objektorientierte Paradigma & für die Realisierung in konkreten objektorientierten Sprachen 3. Zusammenfassung & Fazit 29 / 30 Vielen Dank für Eure Aufmerksamkeit! Seminar Übersetzung von künstlichen Sprachen Realisierung Mehrfachvererbung g : GUIObjekt int farbtiefe GUIObjekt virtual Zeichne() Figur GUIObjekt Figur Linie Linie Rechteck f : Figur l : Linie virtual Flaeche() Int[][] Koordinaten virtual Skalieren() Zeichne() Rechteck b.1) Mehrfache Instantiierung (von GUIObjekt) virtual Skalieren() GUIObjekt Figur k : Kreis r : Rechteck Flaeche() Flaeche() Zeichne() a) Vererbungsgraph (Diamant) Figur Linie Rechteck Linie Rechteck b.2) Einfache Instantiierung (von GUIObjekt) Zurück 2. Übersetzung // Vererbung 31 / 30 Mehrfachvererbung Beispiel: Figur* f = new Rechteck(); f->Zeichne(); // Linie::Zeichne() auf g : GUIObjekt int farbtiefe virtual Zeichne() Nachricht im Pfad: GUIObjekt <- Figur <- Rechteck Aufruf im Parallel-Pfad: GUIObjekt <- Linie <- Rechteck f : Figur l : Linie virtual Flaeche() Int[][] Koordinaten virtual Skalieren() Zeichne() virtual Skalieren() Folgt Regeln der Polymorphie Aber: Programmierer hätte erwarten können, dass GUIObjekt::Zeichne() aufgerufen wird (falls Pfad nicht offen liegt – Teamarbeit, Bibliothek) r : Rechteck Flaeche() Zurück 2. Übersetzung // Vererbung 32 / 30