Einführung in die Systemprogrammierung Virtuelle Methodentabellen Prof. Dr. Christoph Reichenbach Fachbereich 12 / Institut für Informatik 9. Juli 2015 Struktur der Objektorientierung Ein wenig Java: public class Tier { int alter; // Definition der Klasse „Tier“ // Feld (wie in struct) Tier() { this.alter = 0; } // Konstruktor (erzeugt Objekt) int getAlter() { // Methode (ähnlich Funktion) return this.alter; } void tuEtwas() {} } Objektorientierung versus C Grundelemente typischer objektorientierter Sprachen I Klasse: I I I I Konstruktor: I I I I Ähnlich struct: Beinhaltet Felder Beinhaltet außerdem Konstruktoren und Methoden Kann Felder und Methoden erben Ähnlich wie malloc() plus Initialisierung Schreibt Zeiger auf Typdeskriptor in Speicherobjekt Erzeugt Objekt/Instanz der Klasse Methode: I I I Ähnlich Funktion Hat zusätzlichen, meist impliziten Parameter (Java: this, Python: self) this zeigt immer auf Instanz der umgebenden Klasse oder einer Subklasse Klassen und Vererbung public class Tier { int alter; Tier() { this.alter = 0; } int getAlter() { return this.alter; } void tuEtwas() {} } static void machwas(Tier t) { // Was passiert hier? t.tuEtwas(); } machwas(new Katze()); machwas(new Kuh()); public class Katze extends Tier { void tuEtwas() { System.out.println("Miau"); } } public class Kuh extends Tier { void tuEtwas() { System.out.println("<wiederkau>"); } } Wie kann machwas wissen, welche Methode es aufruft? Implementierung von „Tier“ Java public class Tier { int alter; Tier() { this.alter = 0; } int getAlter() { return this.alter; } void tuEtwas() {} } Äquivalentes C typedef struct { typdeskriptor td; int alter; } Tier; typdeskriptor TD_TIER = ...; Tier *new__Tier() { Tier *t = malloc(sizeof(Tier)); t->td = TD_TIER; t->alter = 0; return t; } int Tier__getAlter(Tier *this) { return this->alter; } void Tier__tuEtwas(Tier *this) {} Implementierung von „Katze“ Java Äquivalentes C public class Katze typedef Tier Katze; extends Tier { // gleiche Struktur // int alter; (geerbt) typdeskriptor TD_KATZE = ...; Katze() {} void tuEtwas() { System.out.println( "Miau"); } } Katze *new__Katze() { Tier *t = malloc(sizeof(Katze)); t->td = TD_KATZE; t->alter = 0; return t; } void Katze__tuEtwas(Katze *this) { printf("Miau\n"); } Methodentabelle Java Typ Konstruktor getAlter tuEtwas Tier (C) Tier new__Tier Tier__getAlter Tier__tuEtwas Katze (C) Katze new__Katze Tier__getAlter Katze__tuEtwas Kuh (C) Kuh new__Kuh Tier__getAlter Kuh__tuEtwas I tuEtwas hat eine eigene Implementierung für jede Klasse I getAlter ist vererbt: Jede Klasse benutzt die gleiche Implementierung Lösung mit Fallunterscheidung Java class Tier ... class Katze ... class Kuh ... static void machwas(Tier t) { t.tuEtwas(); } I Äquivalent in C void machwas(Tier *t) { if (t->td == TD_KATZE) Katze__tuEtwas((Katze *) t); else if (t->td == TD_KUH) Kuh__tuEtwas((Kuh *) t); else Tier__tuEtwas(t); } Im Prinzip korrekt, aber: I I I Bei vielen Subklassen große Mengen Code Sicht auf ganzes Programm nötig Jede neue Subklasse erzwingt globale Neuübersetzung Diese Lösung wird normalerweise nicht verwendet Alternative Lösung: Kreative Nutzung des Typdeskriptors I I Wir haben bisher nicht diskutiert, wie der Typdeskriptor aussieht Eine Möglichkeit: Struktur mit expliziter Methodentabelle 0 1 2 I I Typbeschreibung etc. getAlter tuEtwas Eintrag 0 speichert Typinformationen (zur Laufzeit-Typprüfung, Konvertierung, Reflektion etc.) (hat meist andere Struktur als folgende Einträge) Einträge 1 und folgende speichern Funktionszeiger Virtuelle Methodentabelle Tier k0 = new Kuh(); Tier k1 = new Katze(); Tier k2 = new Katze(); td alter: 0 0 1 2 Kuh Tier__getAlter Kuh__tuEtwas 0 1 2 Katze Tier__getAlter Katze__tuEtwas td alter: 0 td alter: 0 Wichtige Rolle des Typdeskriptors in OO-Sprachen ist Methodentabelle Virtuelle Methodentabellen in C Wir verschieben den Typdeskriptor in „virtuelle Methodentabelle“: typedef struct { Tier_vtbl *vtbl; int alter; } Tier; typedef Tier Katze; Katze *new__Katze() { Katze *katze = malloc( sizeof(Katze)); katze->vtbl = &vtbl_KATZE; katze->alter = 0; return katze; } void machwas(Tier *t) { t->vtbl->tuEtwas(t); } typedef struct { typdeskriptor td; int (*getAlter)(Tier *); void (*tuEtwas)(Tier *); } Tier_vtbl; const Tier_vtbl vtbl_TIER = { .td = TD_TIER, .getAlter = Tier__getAlter, .tuEtwas = Tier__tuEtwas }; const Katze_vtbl vtbl_KATZE = { .td = TD_KATZE, .getAlter = Tier__getAlter, .tuEtwas = Katze__tuEtwas }; Subtypen und Methodentabellen vtbl alter: 0 fell: 42 0 1 2 3 Katze Katze__getAlter Tier__getAlter Katze__tuEtwas Katze__jageMaus Virtuelle Methodentabellen und Trennung von Objekt und Typdeskriptor erlauben Subtypen: I I I Methoden zu überschreiben Neue Methoden einfach hinzuzufügen (einfache Vererbung) Neue Felder hinzuzufügen Zusammenfassung: Subklassen und dynamische Bindung I I I I I Subklassen können Methoden ihrer Elternklassen überschreiben Methoden werden in virtueller Methodentabelle gespeichert Jede Klasse hat eigene virtuelle Methodentabelle Jedes Objekt hat Zeiger auf Methodentabelle seiner Klasse Subklassen dürfen: I I I Methoden ändern: Anderer Eintrag in Methodentabelle Methoden hinzufügen: Neuer Eintrag am Ende der Methodentabelle Felder hinzufügen: Neuer Eintrag am Ende der Typstruktur