Verständnisproblem bei Adressen von vererbten Klassenzeigern
-
EDIT: Meine Frage steht am Schluss!
In einem meiner Bücher wurde eine Übungsfrage zum Zeigervergleich von abgeleiteten Klassen gestellt.
Da das Buch nur oberflächlich auf die implizite Konvertierung einging, ich Zitiere:
Für Objekte von Klassen, zwischen denen ein public-Ableitungsverhältnis besteht, bzw. für Zeiger und Referenzen auf Objekte solcher Klassen führt der Compiler bei Bedarf implizite Konvertierungen durch, allerdings mit gewissen Einschränkungen, in sofern nämlich als zwar:
* ein Objekt der abgeleiteten Klasse in ein Objekt der Basisklasse
* ein Zeiger (oder eine Referenz) auf ein Objekt der abgeleiteten Klasse in einen Zeiger (bzw. eine Referenz) auf ein Objekt der Basisklasseumgewandelt wird.
kann dies bei Bedarf bestehen, ein Bedarf den ich aber nicht kenne. Im weiteren wurde auch nicht auf den Vergleich oder den Vergleichsoperator zwischen Pointern von abgeleiteten Klassen eingegangen. Daher habe ich den Lösungsteil bemüht und festgestellt das der Compiler Pointer von abgeleiteten Klassen implizit in einen Basisklassen-Typen konvertiert.
Soweit so gut.
Jetzt zum Problem:
Die Adressen von "Ein und Demselben" Objekt können variieren... ! Und das habe ich dann wirklich nicht richtig verstanden.
Ein Beispiel:
#include <iostream> using namespace std; class X { public: int i; X() { cout << "Konstruktor X" << endl; } }; class Y: public X { public: int j; Y() { cout << "Konstruktor Y" << endl; } }; class XA { public: int i; XA() { cout << "Konstruktor XA" << endl; } }; class YA { public: int j; YA() { cout << "Konstruktor YA" << endl; } }; class ZA : public XA, public YA { public: int k; ZA() { cout << "Konstruktor ZA" << endl; } }; int main() { Y y; X* x = &y; Y* py = &y; cout << "Y*:: py zeigt auf Adresse: " << py << endl; cout << "X*:: x zeigt auf Adresse: " << x << endl; cout << "(Y*:: py == X*:: px) == " << ((py==x)?"Wahr":"Unwahr")<< endl; cout << endl; ZA z; XA* xa = &z; ZA* za = &z; cout << "XA*::xa zeigt auf Adresse: " << xa << endl; cout << "ZA*::za zeigt auf Adresse: " << za << endl; cout << "(XA*::xa == ZA*::za) == " << ((xa==za)?"Wahr":"Unwahr")<< endl; cout << endl; cout << endl << "Soweit so klar! - ABER:" << endl << endl; YA* ya = &z; cout << "YA*::ya zeigt auf Adresse: " << ya << endl; cout << "ZA*::za zeigt auf Adresse: " << za << endl; cout << "(YA*::ya == ZA*::za) == " << ((ya==za)?"Wahr":"Unwahr")<< endl; cout << endl; return 0; }Und der Output:
Konstruktor X Konstruktor Y Y*:: py zeigt auf Adresse: 0x7fff12bb6340 X*:: x zeigt auf Adresse: 0x7fff12bb6340 (Y*:: py == X*:: px) == Wahr Konstruktor XA Konstruktor YA Konstruktor ZA XA*::xa zeigt auf Adresse: 0x7fff12bb6300 ZA*::za zeigt auf Adresse: 0x7fff12bb6300 (XA*::xa == ZA*::za) == Wahr Soweit so klar! - ABER: YA*::ya zeigt auf Adresse: 0x7fff12bb6304 ZA*::za zeigt auf Adresse: 0x7fff12bb6300 (YA*::ya == ZA*::za) == WahrMeine zwei Fragen:
-
Warum ist das Adressen-Offset nur bei YA <-> ZA vorhanden? Und nicht bei allen anderen Pointern auch, aus dem Lehrbuch weiß ich das der Zeiger von YA auf ein Teilobjekt von ZA zeigt, aber genau das macht doch der Zeiger von XA auch, oder?
-
Und warum bei der Einfachvererbung solche Adress-Offsets nicht entstehen, wie beim Zeiger von X der auf ein Objekt von Y zeigt das von X abgeleitet wurde. Damit müsste X ja auch nur auf ein "Teilobjekt" von Y zeigen ?!
Danke schon mal im voraus!
-
-
weil dein ZA so aussieht:
/------\ |/----\| || || || XA || || || |\----/| | | |/----\| || || || YA || || || |\----/| | | | ZA | | | \------/wenn du jetzt auf den YA zugreifst, dann wird der pointer auf das YA teilobjekt
gesetzt. beim vergleich wird der andere pointer ebenfalls konvertiert, und dann
verglichen. daher gibt er "wahr" aus.edit:
die umwandlung nach XA ändert die addresse nicht, nur nach YA
-
Hallo helferlein
Danke für deine Antwort, das mit dem inneren Aufbau des Obejkts habe ich verstanden, aber hast du auch meine Fragen am Schluss gelesen - die interessieren mich nämlich.
-
Zu 1.
Da ZA von den beiden Klassen XA und YA ableitet, so liegen diese ja hintereinander im Speicher (erst die Member von XA, dann die von YA und schließlich noch die von ZA selber - je nach Implementierung kann die Reihenfolge variieren). Wenn du mittels "YA* ya = &z;" dir nur den YA-Anteil herausholst, so kriegst du halt den entsprechenden Zeiger mit dem Offset. Würde der Compiler den Basiszeiger des Objektes zurückgeben, so würden bei einem weiteren Memberzugriff (z.B. ya->j) intern auf ein falsches Member zugegriffen werden (und das wäre fatal!).
zu 2.
Wie oben schon gesagt, liegt es an der Reihenfolge der Klassen. Bei Einfachvererbung kommt zuerst wohl die Basisklasse, dann erst die Member der abgeleiteten Klasse.
Laß dir einfach mal &y.i und &y.j ausgeben...P.S: Beim letzen Vergleich wird der Zeiger za erst in den Basisklassenzeiger YA* verwandlet (und dann erst verglichen), d.h. ya == (YA*)za
-
Th69 schrieb:
Da ZA von den beiden Klassen XA und YA ableitet, so liegen diese ja hintereinander im Speicher (erst die Member von XA, dann die von YA und schließlich noch die von ZA selber - je nach Implementierung kann die Reihenfolge variieren). Wenn du mittels "YA* ya = &z;" dir nur den YA-Anteil herausholst, so kriegst du halt den entsprechenden Zeiger mit dem Offset. Würde der Compiler den Basiszeiger des Objektes zurückgeben, so würden bei einem weiteren Memberzugriff (z.B. ya->j) intern auf ein falsches Member zugegriffen werden (und das wäre fatal!).
Wenn das stimmen würde was du im letzten Satz sagst dann hätte ich jetzt das Problem, siehe meinen Output:
XA*::xa zeigt auf Adresse: 0x7fff12bb6300 ZA*::za zeigt auf Adresse: 0x7fff12bb6300XA und ZA zeigen auf das selbe Objekt und Adresse, was wird also aufgerufen was hinter diesen Adressen steht, XA oder ZA ?
-
Vielleicht sollte ich mich einfacher ausdrücken, also:
/------\ <-- warum zeigt XA, ZA Pointer hier hin? |/----\| || || || XA || || || |\----/| | | |/----\|<-- aber der ZY Pointer zeigt hier hin? || || || YA || || || |\----/| | | | ZA | | | \------/XA*::xa zeigt auf Adresse: 0x7fff12bb6300 ZA*::za zeigt auf Adresse: 0x7fff12bb6300 YA*::ya zeigt auf Adresse: 0x7fff12bb6304 <-- Speicherstelle nach ZA und XADas versuche ich nämlich gerade zu verstehen.
-
Der Groschen ist gefallen!
/------\ <-- XA, ZA zeigen auf Ihren Container-Anfang |/----\| der bei XA und ZA gleich ist! (siehe Skizze) || || || XA || || || |\----/| | | |/----\|<-- der ZY Objekt-Container hat natürlich ein Offset || || zum Anfang, in der Breite von XA Daten-Elementen || YA || (sizeof(int) == 4) || || |\----/| | | | ZA | | | \------/Manchmal dauert's bei mir halt ein bissel länger als bei Anderen.
Danke an Alle und ein besonderers Danke an helferlein für die aufschlussreiche Zeichnung.

-
Und hier noch ein kleiner Nachtrag zur Einfachvererbung:
class X { public: int i; }; class Y: public X { public: int j; }; class Z: public Y { public: int k; };/---------\ <-- X, Y und Z zeigen auf Ihren Container-Anfang |/-------\| der bei allen Klassen gleich ist! (siehe Skizze) ||/-----\|| ||| ||| ||| X ||| ||| ||| ||\-----/|| || || || || || Y || // Y erbt von X und setzt X an seinen Adress-Anfang || || |\-------/| | | | Z | // Z erbt die Container Struktur von Y und setzt diesen | | an seinen Adress-Anfang \---------/Also folglich wird bei der Einfachvererbung IMMER eine feste Adresse für ALLE Klassen-Zeiger dieses Objektes geben und damit sind verschiedene Adressen für ein und dasselbe Objekt nicht möglich.
Im Gegensatz zur Mehrfachvererbung wo verschiedene Adressen für ein und dasselbe Objekt möglich sind.
-
DeepCopy schrieb:
Also folglich wird bei der Einfachvererbung IMMER eine feste Adresse für ALLE Klassen-Zeiger dieses Objektes geben und damit sind verschiedene Adressen für ein und dasselbe Objekt nicht möglich.
Das ist möglich, aber nicht zwingend. Vom Verhalten einer einzigen Implementation zu verallgemeinern ist bestenfalls riskant.
Wahr ist allerdings, dass eine Menge schlechter Code existiert, der von dieser Identität ausgeht, insofern ist nicht damit zu rechnen, das die Compiler an dieser Stelle verändert werden - effizient ist das Verfahren ja ohnehin.
In jedem Falle ist es schlechter Stil, sich diese Identität ohne Not zu Nutze zu machen.DeepCopy schrieb:
Im Gegensatz zur Mehrfachvererbung wo verschiedene Adressen für ein und dasselbe Objekt möglich sind.
Ein und dasselbe Objekt hat genau eine Adresse. Was abweichen kann, sind die Adressen eines Objektes und der darin enthaltenen Subobjekte (nur um die Terminologie klarzustellen).
-
Nur der Form halber:
DeepCopy schrieb:
aus dem Lehrbuch weiß ich das der Zeiger von YA auf ein Teilobjekt von ZA zeigt
Ansonsten gebe ich dir recht, ist wahrscheinlich voreilig zu behaupten das bei der Einfachvererbung alle (Teil-)Objekte an einer Adresse ausgerichtet werden.
Aber wie du schon sagtest:
camper schrieb:
ist nicht damit zu rechnen, das die Compiler an dieser Stelle verändert werden
879 Seiten komplexen C++ Standard um unsere Verträge und Programme zu schützen. Da hat man noch genug Platz zum philosophieren!
In diesem Sinne
Gruß DeepCopy