2012-04-07 14 views
3

Bitte ignorieren Sie die # include Teile vorausgesetzt, sie sind korrekt durchgeführt. Dies könnte auch implementierungsspezifisch sein (aber auch das Konzept von Vtables), aber ich bin nur neugierig, da es mich dabei unterstützt, die Mehrfachvererbung zu visualisieren. (Ich verwende MinGW 4.4.0 by the way)C++ Mehrere Vererbung Speicher Adressierung Problem

Anfangscode:

class A { 
public: 
    A() : a(0) {} 
    int a; 
}; 

//Edit: adding this definition instead 
void f(void* ptrA) { 
    std::cout<<((A*)ptrA)->a; 
} 
//end of editing of original posted code 

#if 0 
//this was originally posted. Edited and replaced by the above f() definition 
void f(A* ptrA) { 
    std::cout<<ptrA->a; 
} 
#endif 

diese kompiliert und Objektcode erzeugt.

in einer anderen Übersetzungseinheit I (nach der Aufnahme der Header-Datei für obigen Code) verwenden:

class C : public B , public A { 
public: 
    int c; 
}objC; 

f(&objC); // ################## Label 1 

Speichermodelles für ObjC:

//<1> stuff from B 
//<2> stuff from B 
//<3> stuff from A : int a 
//<4> stuff from C : int c 

&objC Startadresse < 1 enthält> im Speichermodell angenommen Wie/wann verschiebt der Compiler es zu < 3>? Tritt es während der Überprüfung des Anrufs bei Label 1?

EDIT ::

seit Lable 1 scheint ein Geben weg zu sein, nur um es ein wenig dunkler für den Compiler zu machen. Bitte beachten Sie den oben genannten Code. Wann macht der Compiler und wo?

+0

Klasse B soll wie Klasse A aussehen? Vielleicht könnten Sie auch seinen Code hinzufügen. Ziemlich interessante Frage in der Tat. – Haatschii

+1

@Haatschii: Klasse B könnte alles sein. Sie können beliebige Daten unter <1> und <2> des Speichermodells einfügen. Nur aus Gründen der Einfachheit sollten Sie keine virtuellen Funktionen und keine virtuelle Vererbung annehmen. – ustulation

Antwort

1

Kurzantwort: Der Compiler passt die Zeigerwerte während der Umwandlungsoperationen an, wenn er die Beziehung zwischen der Basisklasse und der abgeleiteten Klasse kennt.

Nehmen wir an, die Adresse Ihrer Objektinstanz der Klasse C hatte die Adresse 100. Sagen wir also sizeof (C) == 4. Ebenso wie die Größe von (B) und die Größe von (A).

Wenn ein gegossener geschieht, wie die folgenden:

C c; 
A* pA = &c; // implicit cast, preferred for upcasting 
A* pA = (A*)&c; // explicit cast old style 
A* pA = static_cast<A*>(&c); // static-cast, even better 

Der Zeiger-Wert von pA wird die Speicheradresse von c plus die von dem „A“ beginnt, in diesem Fall in C. versetzt werden kann, wird pA Referenzspeicheradresse 104 unter der Annahme, dass Größe von (B) auch 4 ist.

All dies gilt für die Übergabe eines abgeleiteten Klassenzeigers an eine Funktion, die einen Basisklassenzeiger erwartet. Die implizite Umwandlung wird ebenso wie die Zeigeroffseteinstellung auftreten.

Ebenso für Downcasting:

C* pC = (C*)(&a); 

Der Compiler wird während der assigment kümmern den Zeigerwert einzustellen.

Die eine „Gotcha“, um all dies ist, wenn eine Klasse nach vorne, ohne eine vollständige Erklärung erklärt:

// foo.h 
class A; // same as above, base class for C 
class C; // same as above, derived class from A and B 

inline void foo(C* pC) 
{ 
     A* pA = (A*)pC; // oops, compiler doesn't know that C derives from A. It won't adjust the pointer value during assigment 
     SomeOtherFunction(pA); // bug! Function expecting A* parameter is getting garbage 
} 

, die ein echter Fehler ist!

Meine allgemeine Regel. Vermeiden Sie die alte "C-style" -Wiedergabe und bevorzugen Sie den static_cast-Operator oder verlassen Sie sich einfach auf implizites Casting, ohne dass ein Operator das Richtige tun muss (für Upcasts). Der Compiler gibt einen Fehler aus, wenn das Casting nicht gültig ist.

+1

interessant. Aber warum sollte ein Compiler erst dann eine verzögerte Kompilierung durchführen, wenn er eine Kompilierungseinheit findet, die Mehrdeutigkeiten in solchen speziellen Fällen beseitigt? Und bleibt der erste Teil Ihrer Antwort auch für meinen bearbeiteten Code gültig? (Geht es nun durch die vollständige f (...) Definition im Objektcode?) – ustulation

+0

Wenn Sie den static-cast-Operator verwenden, wird der Compiler beim Auftreten von Mehrdeutigkeiten oder ungültigen Cast fehlgehen. In C, wo Vererbung und Mehrfachvererbung nicht existieren, sind Blind-Umwandlungen zwischen Strukturtypen wie (A *) & c vollkommen gültig und werden oft verwendet. Daher muss C++ mit dieser Art von Legacy-Code umgehen, hat aber später den static_cast-Operator erfunden, um die Dinge sicherer zu machen. – selbie

+0

Da in Ihrem bearbeiteten Code der abgeleitete Klasseninstanzzeiger (implicitly) beim Aufruf der Funktion f auf void * umgesetzt wird, ist die Rückübertragung auf (A *) unsicher. Der Zeigerwert wird keine Offset-Konvertierung auslösen. Du hast im Wesentlichen ein "reinterpret_cast" -Verhalten gemacht, was dem Ausdruck "ptrA = (A *) ((void *) ptrC) entspricht"; Das ist nicht dasselbe wie "ptrA = ptrC" zu sagen. Wenn jemand also "f" mit einem Parameter aufruft, der ein von A abgeleitetes Objekt repräsentiert, aber mehrfache Vererbung, besteht ein Risiko für einen Fehler. – selbie

1

Ja, Sie sind ziemlich richtig.

Um vollständig die Situation zu verstehen, muss man wissen, was der Compiler an zwei Punkten weiß:

  1. Bei Label 1 (wie Sie bereits identifiziert haben)
  2. Innen Funktion f()

    (1) Der Compiler kennt das genaue binäre Layout von C und A und wie man von C * nach A * umwandelt, und zwar an der Aufrufstelle (Label 1)

    (2) Innenfunktion f() jedoch der Compiler nur (muss) über A * wissen und beschränkt sich so auf Mitglieder von A (int a in diesem Fall) und kann nicht darüber verwirrt sein, ob die bestimmte Instanz Teil von irgendetwas anderem ist oder nicht.

+0

danke! Hat meine Bearbeitung des Codes einen Unterschied? – ustulation

+0

Äh, ja, der ganze Unterschied in der Welt. Sie haben nur die Hinweise entfernt, die der Compiler braucht, um das Richtige zu tun. Es könnte sein, dass das Programm in Teile eines B eintauchen wird. – quamrana

+0

Cool! Also versaut es es für den Compiler, ohne auch nur etwas Illegales/Undefiniertes oder ohne Cheaten zu machen (wie Typ "diesen" Zeiger auf nicht-konstanten Typ umwandeln und Elementdaten innerhalb einer const Member-Funktion verändern) :) Und vielleicht mehr Würmer mit virtuals (Funktionen und Basen) !! – ustulation