Inhalt
Topic:.ObjectRef_All.ObjectRef_C.
In C++ gibt es zwei Arten von Referenzen, und die Möglichkeit, eine Instanz direkt anzugeben
Type*
ist ein Zeiger auf Daten des Types Type. Es ist möglich, dass die Daten tatsächlich von einem abgeleitetem Typ sind, Type
ist eine Basisklasse oder ein Interface auf die Daten.
Type&
ist genau das gleiche, aus Sicht der Abarbeitung (Maschinencode). Für den Compiler (Syntax) ergeben sich zusätzliche Möglichkeiten,
insbesondere überladene Operatoren. Type& ist eine spezielle C++-Erfindung.
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
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.
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:
Index des Eintrages des Rückwärtszeigers auf die referenz in den Daten des RuntimeHeap. Dieser Rückwärtszeiger wird benötigt für den Garbage Collector. Der in der Referenz gespeicherte Index erleichtert das Auffinden des Eintrages (Rechenzeitersparnis), um die Rückwärtsreferenz zu löschen wenn die Referenz geändert wird. Der Index ist eine Zahl im niedrigem Integer-Bereich (0 bis 255 wäre möglich bei entsprechender Aufbereitung, ansonsten bis zur maximalen Größe eines Blockes im Runtimeheap, diese liegt garantiert unter 65535, benötigt also maximal 16 bit).
Index für die virtuelle Tabelle. Hier wären 32 bit notwendig, wenn der Zeiger auf die virtuelle Tabelle als absolute Adresse direkt gespeichert werden würde. Jedoch könnten auch 8 bit ausreichen, wenn entweder die Anzahl der virtuellen Methoden im System klein (kleiner 256) ist oder wenn dieser Index bezogen auf den abgeleiteten Typ geführt wird, die Adresse der virtuellen Tabelle selbst also noch woanders (innerhalb der Reflection-Tabelle) steht. Jedenfall reichen 16 bit.
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:
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.
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.
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
entweder das Object hat aus einer aufrufenden Ebene eine Rückwärtsreferenz. Damit wird es vom GC nicht entsorgt.
Oder das Object ist überhaupt nicht rückwärtsreferenziert, weil neu angelegt. Damit wird es auch nicht entsorgt. Hier hat man das gleiche Problem wie wenn das Object in der selben Ebene angelegt und nicht rückwärtsreferenziert wird, also keine Besonderheit wegen dem Aufruf.
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.
Topic:.ObjectRef_All.MakroDescription.
Bei der Definition der Referenz-Makros wird berücksichtigt, dass die CRuntimeJavalike in verschiedenen Arten genutzt werden kann:
Ohne die Verwendung eines Garbage Collectors, in C++, ohne Interfaces in C
Mit Verwendung eines Garbage Collectors oder mit Interfaces in C
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:
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
.
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.
Folgende Inhalte sind unmittelbar aus dem Headerfile Object_Jc.h
entnommen und wiederholen die obige Darstellung:
Define: ReferenceUsingSimple
Folgende Inhalte sind unmittelbar aus dem Headerfile ObjectRef_Jc.h
entnommen und wiederholen die obige Darstellung:
Define: ReferenceUsing