CRuntimeJavalike - Referenzen

CRuntimeJavalike - Referenzen

Inhalt


1 Referenzen in C und C++

Topic:.ObjectRef_All.ObjectRef_C.

In C++ gibt es zwei Arten von Referenzen, und die Möglichkeit, eine Instanz direkt anzugeben

In C++ kann man ein Object entweder direkt ansprechen:

 Type Object; Object.doSomething();

oder man kann das Object über Referenzen ansprechen:

 Type Object; Type* pObject = &Object; Type& rObject = Object;
 pObject->doSomething; rObject.doSomething;

Dabei verschleiert die Benutzung der Type&-Referenz den Unterschied zwischen direkten Ansprechen eines Objekts und Ansprechen über Refernz.

Eine Referenz kann immer vom Typ einer Basisklasse sein, die eine Instanz (ein Object) von einer abgeleiteten Klasse zeigert:

 BaseType* pObject;
 BaseType& rObject;
 pObject = &Object;
 rObject = Object; //Object ist vom abgeleitetem Typ Type

2 In Java werden Instanzen grundsätzlich nur über Referenzen angesprochen

Topic:.ObjectRef_All.ObjectRef_Java.

In Java ist die Welt gegenüber C++ vereinfacht: Es gibt nur Referenzen. Wenn von Java allgemein gesagt wird, es gäbe keine Zeiger, dann stimmt das insoweit: Es gibt nicht die in C /C++ bekannte und durchaus kritikwürdige Zeigerarithmetik. Ansonsten gibt es aber nur Zeiger.

 BaseType object = new Type();

Mit dieser Java-Zeile wird ein Object vom Typ Type angelegt, und ist danach nur über dessen Basisklasse BaseType, und zwar über die Referenz object bekannt. In Java gibt es keine Möglichkeit, eine Instanz (ein Object) direkt anzusprechen. Damit kann ein Object auch keinen Namen haben, es gibt gegebenenfalls mehrere Referenzen auf das Object, die verschiedene Namen haben können. Ein Name kann nur existieren als Eigenschaft (Variableninhalt) des Objects, nicht im Kontext des Compilers.


3 Referenzen in CRuntimeJavalike und Java2C

Topic:.ObjectRef_All.ObjectRef_CRuntimeJavalike.

Bei Nutzung der CRuntimeJavalike-Library sind Referenzen (= Zeiger) wie in C zu handhaben, wenn weder dynamisches Binden (virtuelle Methoden) noch der Garbage Collector des Runtimeheap genutzt wird. Wird jedoch mindestens eines dieser Features genutzt, dann ist eine Referenz eine Datenstruktur der Art:

 typedef struct Type_r_t
 { /** The base data of references*/
   ObjectRefValues_c refbase;
   /** The object be referenced from here*/
   struct Type_c_t* ref;
 }Type_r;

Der Zeiger ref ist der eigentliche Zeiger auf die Daten (gegebenenfalls vom Interface- oder Basistyp). refbase enthält weitere Informationen, die nicht in den Daten, sondern in der Referenz gespeichert werden:


4 Aufbau einer Referenzstruktur - passt in zwei Prozessor-Register

Topic:.ObjectRef_All.ObjectRef_2Register.

Die Diskussion über die Speichergröße muss deshalb intensiv geführt werden, weil die Effektivität der Maschinencodeabarbeitung stark davon abhängt:


4.1 Erweiterte Referenz als return by value

Topic:.ObjectRef_All.ObjectRef_2Register.returnByValue.

Referenzen müssen häufig als Returnwerte von Methoden zurückgegeben werden, und zwar nicht referenziert, sondern "return by value":

 Type_r createObject()
 { Type_r ret;
   DerivatedType* obj = new_DerivatedType();  //Object vom abgeleitetem Typ anlegen
   doSomething_DerivatedType(obj, ...);       //aufbereiten
   setRef_DerivatedType_Type(&ret, obj);      //setzen der Referenz
   return ret;                                //Rückgabe der Referenz
 }

Im Beispiel wird von einer Methode erwartet, dass sie ein Object von einem abgeleitetem Typ anlegt (welcher, ... ggf. in der Methode bestimmt), diesen initialisiert aber dann eine Referenz auf einen Basistyp oder ein Interface zurückliefert, dass außen verwendet werden soll.

Grundsätzlich falsch ist es, zu programmieren:

 Type_r* createObject()
 { Type_r ret;
   ...
   return &ret;                              //Rückgabe der Referenz
 }

Das läuft zwar durch den Compiler, und könnte zunächst sogar funktionieren. Aber die Daten der Referenz liegen im Stack, in einem Bereich, der freigegeben ist und anderweitig wiederverwendet wird (unterhalb der aktuellen Stackgrenze).

Die Frage ist zu stellen: Wie verhält sich ein Compiler bei "return by value" einer Struktur. Zulässig ist dieses Konstrukt in C jedenfalls. Bei einer größeren Struktur wird der Inhalt vom Stackbereich, die im Beispiel von der Variable ret belegt wird, in einen temporären Stackbereich kopiert, der für die Rückgabe reserviert ist. Da in der Regel der Inhalt der Struktur irgendwo gespeichert werden soll, also:

 reference = createObject();

hier in reference, das kann ein Element einer anderen Struktur sein, oder vielleicht wieder eine stacklokale Variable, wird der Inhalt nochmals kopiert. Das heißt, es sind in der Regel zwei Speicherkopier-Befehlssequenzen notwendig.

Viele Compiler können aber ein "return by value" einer einfachen Struktur, die in zwei Prozessorregister passt, über Prozessorregister zurückgeben. Selbst der Visual-Studio-Compiler, der an sich vieles über den Stack erledigt (da im Zielprozessor der i86-Famile effektive Speicherzugriffsmechanismen vorhanden sind), erledigt die Rückgabe einer einfachen Struktur auf diese Weise, über die Prozessorregister eax und edx.


4.2 Erweiterte Refenenz in 2 Prozessorregistern abbildbar

Topic:.ObjectRef_All.ObjectRef_2Register.2Register.

Daher gilt, eine Referenz-Struktur muss so einfach aufgebaut sein, dass ein Compiler sie in der Regel über Prozessorregister übergeben kann. Daher besteht die Struktur einer Referenz aus zwei Werten:

 typedef struct Type_r_t
 { /** The base data of references*/
   ObjectRefValues_c refbase;
   /** The object be referenced from here*/
   struct Type_c_t* ref;
 }Type_r;

Der Typ ObjectRefValues_c ist eine einfach int32-Variable, oder eine int16-Varaible, wenn der Einsatz in einem "kleinem" 16-bit-System erfolgen soll. Der Zeiger ist ebenfalls 32 bit (oder 16 bit) breit, beide Werte passen jeweils in ein Prozessor-Register.


4.3 Erweiterte Referenz als call by value

Topic:.ObjectRef_All.ObjectRef_2Register.returnByValue.

Bei einem Return besteht schlichtweg die Notwendigkeit eines return by value. Bei einer Argumentübergabe hat man die Auswahl: call-by-value oder call-by-reference. Bei einem call-by-reference wird der Zeiger auf die Referenzstruktur übergeben. Das hat folgenden Nachteil:

Wenn das Aufrufargument in einer Kette als Rückgabeargument einer anderen Subroutine auftritt, dann muss man bei call-by-reference die rückgegebene Referenz in einer Referenzvariable zwischenspeichern. Anderes geht es syntaktisch nicht. Dieser Fall tritt durchaus häufig auf, wie beispielsweise:

String_Jc substring = substring_String_Jc(&myString, 2,5);
callRoutine(&substring);

Einfacher wird es mit

callRoutine(substring_String_Jc(&myString, 2,5));

Solche Kettungen (Aufruf einer weiteren Methode in einer Argumentliste) sind zwar ganz allgemein kein so guter Programmierstil, da beim Debuggen auch unübersichtlich, sie sind aber besser weil einfacher lesbar. Wenn die Funktionalität nicht komplex ist, ist die Verkettung auch problemlos überschaubar. Es spricht also einiges dafür.

Ein anderes Argument spricht für die call-by-value-Methode: Es ist maschinentechnisch kein zusätzlicher Aufwand. Da die Referenzstruktur in zwei Prozessor passt, wird sie auch adäquat übergeben: Als zwei getrennte Einträge im Stack. Dort wird sie direkt verwendet. Bei einem call-by-reference liegt dagegen der Referenzwert noch dazwischen, man braucht also einen Speicherzugriff mehr. Hier schlägt das allgemeine Argument gegen call-by-value: Aufwand beim Aufruf wegen copy des Inhaltes, in das Gegenteil um.

Bei einem call-per-value wird nicht ein Registrieren der Rückwärtsreferenz im referenzierten Objekt ausgeführt (BlockHeap_GarbageCollection). Das ist aber nicht notwendig, denn

Die Konsequente Verwendung des call-by-value für Refernezen ist bisher in der CRuntimeJavalike nicht berücksichtigt (Stand August-2007), wird aber nach und nach eingebaut. Damit enfällt dann auch die Notwendigkeit solch unleserlicher Makros wie REFP_REF_Jc oder der REFP_type, es wird also einfacher.


5 Makros für Nutzung von Referenzen

Topic:.ObjectRef_All.MakroDescription.

Bei der Definition der Referenz-Makros wird berücksichtigt, dass die CRuntimeJavalike in verschiedenen Arten genutzt werden kann:

Beide Fälle entsprechen verschiedenen Anwendungsaspekten. Die Referenzen in der oben beschriebenen Art sind nur notwendig wenn entweder der Garbage Collector oder Interfaces mit dynamischen Binden in C benutzt werden. Die daraus sich ergebenden Varianten sind mittels bedingter Compilierung in Object_Jc.h und ObjectRef_Jc.h enthalten. Die ObjectRef_Jc.h wird dabei innerhalb der Object_Jc.h eingezogen, wenn die erweiterten Referenzen benötigt werden. Für die einfache C++-Variante ohne Garbage Collector, bei der die erweiterten Referenzen nicht benötigt werden, sind die Definitionen sind in der Object_Jc.h enthalten, Dann braucht die Object_Ref_Jc.h auch nicht vorhanden zu sein.

Im Fall ohne die Verwendung eines Garbage Collectors und mit C++, (C++-simple) ist die Umsetzung der Makros sehr einfach. Man könnte hier auch ganz gut ohne die Makros leben, sie stellen nur eine (unnötige?) Verkomplizierung dar. Aber wenn die selben Sources auch an anderer Stelle oder später mit Garbage-Collector verwendet werden sollen, dann sollten die Makros gleich von Anfang an eingesetzt werden.

Folgend sind alle Varianten der Referenzmakros hinsichtlich Verwendung und Definition beschrieben:


5.1 Definierende Makros (für Typdefintionen und Konstante)

Topic:.ObjectRef_All.MakroDescription.defining.

Die folgenden Makros werden typisch in Headerfiles verwendet und erzeugen keinen Maschinencode. Bei den Konstanten wird dem Compiler die Initialisierung definierter Daten mitgeteilt ({...}-Konstrukt oder immediate-Angaben).

REFTYPEDEF_Jc(type): Definition eines Referenztypes im Header.

Dieses Makro definiert einen Typ, der eine Referenz auf den angegebenen type darstellt. Das Makro sollte in Headerfiles unmittelbar nach einer Definition einer class oder struct plaziert werden. Nach dieser Definition sind zwei Typbezeichner definiert:

  • REF_type: Referenz des angegebenen Typs.

  • REFP_type: Referenz auf die Referenz des angegebenen Typs, notwendig insbesondere für die Parameterübergabe per Referenz.

Hinweis: Wenn der type ein Interfacetype ist, dann sind mit der Definition über das Makro IFCTYPEDEF_Jc(type) diese beiden Identifier bereits definiert, so dass REFTYPEDEF_Jc(type) hier nicht verwendet werden darf.

Die Realisierung für die verschiedenen Plattformen erfolgt wie folgt:

  • C-GC: typedef struct REF_##TYPE##_t{ ObjectRefValues_Jc refbase; TYPE* ref; } REF_##TYPE; typedef REF_##TYPE* REFP_##TYPE;:

    Der REF_type ist die Struktur der enhanced reference. REFP_type ist die Referenz auf diese Struktur. Die erweiterte Referenz wird in C immer benötigt, entweder wegen dem Garbage Collector, oder wegen dem InterfaceConcept_in_C. Hinweis: bei der Definition über IFCTYPEDEF_Jc(type) ist ref ein Zeiger auf Object_Jc, da es für Interfaces keine Strukturdefinition gibt sondern Interfaces direkt auch als Object_Jc angesprochen werden können.

  • C++-GC: Wie C-GC, auch hier wird die erweiterte Referenz wegen dem Garbage Collector benötigt. Die Definition ist die selbe wie in C-GC. Hinweis: ref ist aber eine Referenz auf den Interfacetype, wenn IFCTYPEDEF_Jc(type) verwendet wird.

  • C++-simple: typedef TYPE* REF_##TYPE; typedef REF_##TYPE REFP_##TYPE;:

    Hier wird keine erweitere Referenz benutzt. Die Referenz ist ein einfacher Zeiger auf den Typ, REFP_type ist identisch definiert, ein einfacher Zeiger.

NULLREF_Jc: Konstanter Wert für eine null-Referenz, als Initialwert für Referenzen.

Bei der Anlage von Referenzen als Stackvariable müssen diese mit 0 initialisiert werden. Das geschieht nach folgendem Schema:

REF_type myRef = NULLREF_Jc;

Die Initialisierung ist wichtig, wenn mit Garbage Collector gearbeitet wird und eine Referenz demzufolge mit SETREF_Jc() belegt wird. Da SETREF_Jc() mit einer bisher anderweitigen Belegung rechnet, die bisherige Rückwärtsreferenz demnach in der vermeintlich referenzierten Instanz löschen will, kann eine nicht-Initialisierung und damit eine zufällige Anfangsbelegung einer Referenz fatal sein. In einer einfachen C++-Umgebung fällt das nicht auf, die Referenz ist bis zu ihrer ersten richtigen Belegung zufällig belegt. Aber auch hier ist eine Intialisierung mit null zu empfehlen: Einerseits werden damit beim Debuggen richtige Verhältnisse gezeigt, andererseits wirkt sich ein Softwarefehler Verwenden vor Erstbelegung in definierter Weise aus.

  • C-GC: {{0}, null}: Das ist eine initiale null-Struktur.

  • C++-GC: Wie bei C-GC.

  • C++-simple: null: Ein imediate-Nullwert genügt hier.

CONSTREF_Jc(OBJ): Konstanter Wert für eine nicht-Interface-Referenz.

Dieses Makro darf nicht für die Intialisierung von variablen verwendet werden, sondern nur wenn konstante Strukturen aufgebaut werden. Das OBJ darf nicht im Blockheap mit Garbage Collector stehen, sondern soll ebenfalls konstant sein. OBJ ist der Zeiger auf die (typisch konstante) Instanz.

  • C-GC: {{0}, OBJ}: Eine etwaiger Rückwärtsreferenz exisitiert nicht. Es verbleibt der Referenzzeiger..

  • C++-GC: Wie bei C-GC.

  • C++-simple: OBJP: Das ist der Zeiger auf das Object selbst.

CONSTREF_ifc_Jc(IFACE, CLASS, OBJ): Konstanter Wert für eine Interface-Referenz.

Die Besonderheit bei konstanten Interface-Referenzen ist, dass der Index für die Position in der virtuellen Tabelle der instanziierten Klasse in der Referenz enthalten sein muss, das gilt für C-GC. Für die anderen Plattformen ist die Umsetzung des Makros einfach. Dieses Makro darf nicht für die Intialisierung von Variablen verwendet werden, sondern nur wenn konstante Strukturen aufgebaut werden. Das OBJ darf nicht im Blockheap mit Garbage Collector stehen, sondern soll ebenfalls konstant sein.

  • IFACE ist aus Anwendungssicht der Typ des Interfaces. Aus Implementierungsicht in C ist es der Indentifier des Elementes für das Interface in der Vtbl der angegebenen Klasse. Diese Elemente sollen gleichnamig mit den Interfacetypen sein. Das Makro funktioniert also nur richtig, wenn die Regeln für die Bildung der Virtuellen Tabellen in C eingehalten werden, siehe InterfaceConcept_in_C. Bei C++ ohne erweiterte Referenzen wird dieses Makroargument nicht benutzt.

  • CLASS ist aus Anwendungssicht der Typ der Klasse entsprechend der Referenz. In der Umsetzung für C wird mit dieser Bezeichnung die Vtbl_##CLASS aufgesucht. . Bei C++ ohne erweiterte Referenzen wird dieses Makroargument nicht benutzt.

  • OBJ ist der Zeiger auf die (typisch konstante) Instanz.

  • C-GC: { ((int)&(((Vtbl_##CLASS*)(0x1000))->IFACE) - 0x1000)/sizeof(Void_MethodVoid), (Object_Jc*)(OBJ) }: Hier wird also mitAdressrechnung im Element refBase der Kostante der notwendige Index berechnet aus Kenntnis der Vtbl_CLASS berechnet.

  • C++-GC: Wie bei C-GC.

  • C++-simple: OBJ: Das ist der Zeiger auf das Object selbst.

CONSTREF_ifc_Jcpp(IFACE, CLASS, OBJ): Konstanter Wert für eine Interface-Referenz.

Der Unterschied zu CONSTREF_ifc_Jc ist, dass nicht mit einer C-like-virtuellen Tabelle gearbeitet wird sondern für den Fall C++-GC (auch bei Vorhandensein der erweiterten Referenz) der C++-Mechanismus des Aufrufes virtueller Methoden benutzt wird. Demzufolge braucht es in dieser Variante keine Vtbl_CLASS zu geben.

  • C-GC: Wie CONSTREF_ifc_Jc

  • C++-GC: { 0, OBJ }

  • C++-simple: OBJ: Der Zeiger auf die Instanz selbst wie bei CONSTREF_ifc_Jc.


5.2 Funktionale Makros

Topic:.ObjectRef_All.MakroDescription.functional.

Diese Makros generieren Maschinencode.

CLEARREF_Jc(REF): Löschen (setzen auf null) einer Referenz, im laufenden Code.

Bei Verwendung des Garbage Collectors muss die Rückwärtsreferenz aus der bisher referenzierten Instanz ausgetragen werden. Wird die Referenz direkt mit null belegt, dann bleibt die Rückwärtsreferenz fäschlicherweise stehen. Damit muss immer CLEARREF_Jc() für diesen Zweck verwendet werden. Wird kein GC verwendet, dann wird ein einfaches null-Setzen ausgeführt.

  • C-GC: {clearBackref_Jc(&((REF).refbase), STACKTRC); (REF).ref = null; (REF).refbase = 0; }: in der Methode clearBackref_Jc() wird die Rückwärtsreferenz gelöscht. Der Mechanismus des Stacktraces muss zugriffsfähig sein, oder STACKTRC muss mit null definiert vorliegen.

  • C++-GC: Wie bei C-GC.

  • C++-simple: REF = null: Die Referenz wird einfach genullt.

SETREF_Jc(REF, OBJP, REFTYPE): Setzen einer Referenz

Bei Verwendung des Garbage Collectors muss die Rückwärtsreferenz aus der bisher referenzierten Instanz ausgetragen werden und die Rückwärtsrefenz in die jetzt gezeigerte Instanz eingetragen werden.

  • C-GC:

    (setBackrefIdxvtbl_Jc(&((REF).refbase), (Object_Jc*)(OBJP), (Class_Jc*)(&reflection__##REFTYPE), STACKTRC)

    (REF).ref = (OBJP)):

    Mit der Methode setBackrefIdxvtbl_Jc() wird die bisherige Rückwärtsreferenz falls vorhanden gelöscht und die neue Rückwärtsreferenz gesetzt. Dabei wird die Reflection-Information übergeben, diese Information muss verfügbar sein. Der Mechanismus des Stacktraces muss ebenfalls zugriffsfähig sein, oder STACKTRC muss mit null definiert vorliegen.

  • C++-GC: Wie bei C-GC.

  • C++-simple: REF = OBJP: Die Referenz wird einfach gesetzt.

REF_Jc(ref): Zugriff auf eine Referenz im laufenden Code

Mit diesem Makro wird der Zugriff auf die Referenz geschrieben. Das Makro liefert syntaktisch das gleiche wie REFP_Jc(refp): einen Zeiger auf die referenzierte Instanz. ref ist eine Referenz-Variable (also vom REF_type).

  • C-GC: ((ref).ref): Der damit gelieferte Zeiger ist vom Typ der Referenz auf die eigentlichen Daten. Hinweis: Bei Interfaces in C ist das eine Referenz auf Object_Jc.

  • C++-GC: Wie bei C-GC. Hinweis: Bei Interfaces ist das der Zeiger auf die Interface-class.

  • C++-simple: (ref): Das ist einfach nur die Referenz selbst.

REFP_Jc(refp): Zugriff auf eine referenzierte Referenz im laufenden Code:

Mit diesem Makro wird der Zugriff auf den Referenzzeiger geschrieben. Das Makro liefert syntaktisch einen Zeiger auf die referenzierte Instanz. refp ist eine Variable vom Typ einer Referenz auf eine Referenz, also vom REFP_type, wie sie typisch bei Argumentübergaben verwendet wird.

  • C-GC: ((refp)->ref): Der damit gelieferte Zeiger ist vom Typ der Referenz auf die eigentlichen Daten. Hinweis: Bei Interfaces in C ist das eine Referenz auf Object_Jc.

  • C++-GC: Wie bei C-GC. Hinweis: Bei Interfaces ist das der Zeiger auf die Interface-class.

  • C++-simple: (refp): Das ist die Referenz selbst.

REFP_REF_Jc(refp): Dereferenzierung eines Referenzzeigers im laufenden Code

Dieses Makro wird benötigt, um von einem Referenzzeiger (REFP_type) auf die erweiterte Referenz zu erhalten. Das ist insbesondere notwendig für die Argumentübergabe an Makros SETREF(ref) usw.

  • C-GC: (*(refp)): Die Referenz auf die enhanced reference wird derefenrenziert.

  • C+-GC: Wie bei C-GC.

  • C++-simple: (ref): Das ist die Referenz selbst.


5.3 Headerfileinhalt, einfache Referenzen

Folgende Inhalte sind unmittelbar aus dem Headerfile Object_Jc.h entnommen und wiederholen die obige Darstellung:


Define: ReferenceUsingSimple


5.4 Headerfileinhalt, erweiterte Referenzen

Folgende Inhalte sind unmittelbar aus dem Headerfile ObjectRef_Jc.h entnommen und wiederholen die obige Darstellung:


Define: ReferenceUsing