2016-05-05 15 views
-3

Ich habe ein Problem im Zusammenhang mit dem Entwurf abgeleiteter Klassen mit Array-Parametern. Ich habe Klasse B abgeleitet von A. Und Klasse BB abgeleitet von AA mit Anordnung von B und A jeweils ...Segmentierungsfehler beim Aufruf der abgeleiteten Klassenmethode

#include <iostream> 

class A 
{ 
public: 
    A(){} 
    virtual void foo(){std::cout<<"foo A\n";} 
    int idx[3]; 
}; 

class B: public A 
{ 
public: 
    B():A(){} 
    void foo(){std::cout<<"foo B\n";} 
    int uidx[3]; 
}; 

class AA 
{ 
public: 
    AA(){} 
    AA(int count){ 
     m_count = count; 
     m_a = new A[count]; 
    } 
    virtual A* getA(){return m_a;} 
    ~AA(){ delete[] m_a;} 
protected: 
    A* m_a; 
    int m_count; 
}; 

class BB: public AA 
{ 
public: 
    BB(int count):AA() 
    { 
     m_count = count; 
     m_a = new B[count]; 
    } 
    B* getA(){return dynamic_cast<B*>(m_a);} 
}; 

int main() 
{ 
    AA* aa = new AA(2); 
    BB* bb = new BB(2); 
    B* b = bb->getA(); 
    B& b0 = *b; 
    b0.idx[0] = 0; 
    b0.idx[1] = 1; 
    b0.idx[2] = 2; 

    B& b1 = *(b+1); 
    b1.idx[0] = 2; 
    b1.idx[1] = 3; 
    b1.idx[2] = 4; 

    std::cout<<bb->getA()[1].idx[0]<<"\n"; //prints 2 
    std::cout<<bb->getA()[1].idx[1]<<"\n"; //prints 3 
    std::cout<<bb->getA()[1].idx[2]<<"\n"; //prints 4 

    AA* cc = static_cast<AA*>(bb); 
    cc->getA()[0].foo(); //prints foo B 

    std::cout<<cc->getA()[1].idx[0]<<"\n"; //prints 4198624 ?? 
    std::cout<<cc->getA()[1].idx[1]<<"\n"; //prints 0 ?? 
    std::cout<<cc->getA()[1].idx[2]<<"\n"; //prints 2 ?? 

    cc->getA()[1].foo(); //segmentation fault 
    delete aa; 
    delete bb; 
    return 0; 
} 

Nach statischer Besetzung BB AA ich nicht die mit dem Indizes mehr als 0 zugreifen kann Wie kann ich dieses Problem lösen? Danke.

+0

ich schlage vor, lesen Sie den C++ zu FAQ-Bereich [Ist ein Array von einer Art-of-Array of Base Abgeleitet] (https://isocpp.org/wiki/faq/proper -inheritance # array-derived-vs-base). – jotik

+0

Dies könnte als [Array Decay Bug] (http://stackoverflow.com/a/37052920/3919155) bezeichnet werden. – jotik

Antwort

0

I 2 Ausgaben im Code sehen:

  1. Da Ihre Klassen sind verantwortlich für die Speicherverwaltung, würde ich vorschlagen, Ihre Destruktoren virtual zu machen, denn wenn man an einem beliebigen Punkt, versucht abgeleitet löschen Klassenobjekt über Basiszeiger, werden die Destruktoren abgeleiteter Klassen nicht aufgerufen. Es sollte kein Problem in Ihrem aktuellen Code sein, aber kann ein Problem in einer Zukunft werden.

D.h.:

int main() 
    { 
    AA* aa = new BB (2); 
    delete aa; 
    } 

Wird nicht die BB::~BB() in Ihrem Fall nennen.

  1. Das Problem, das Sie bemerken, und das Schreiben dieser Frage über.

Nachdem Sie Ihre Variable vom Typ BB*-AA* werfen (auch wenn die Besetzung nicht notwendig ist, können Sie straight-up zuweisen, aufgrund Arten covariant ist) in Zeile:

AA* cc = dynamic_cast<AA*>(bb); 

Ihre Variable cc wird behandelt, als ob sie vom Typ AA* ist (es spielt keine Rolle, dass sie den Laufzeittyp BB* hat, im Allgemeinen - Sie wissen nicht und sollten sich nicht um den genauen Laufzeittyp kümmern). Bei jedem Aufruf virtueller Methoden werden sie über den vtable an den richtigen Typ gesendet.

Und jetzt, warum erhalten Sie seltsame Werte in der Konsole/Segmentierung Fehler gedruckt? Was ist das Ergebnis von cc->getA()? Da die Variable cc als AA* behandelt wird, lautet der Rückgabewert A* (wie oben erklärt, ist der tatsächliche Typ B*, aber aufgrund von - wird eine Vererbungsbeziehung wie A* behandelt). Was ist das Problem, können Sie fragen: Das Array m_a ist in beiden Fällen die gleiche Größe, oder?

Nun, nicht wirklich, um das zu erklären, ich müsste erklären, wie Array-Indizierung in C++ funktioniert, und wie es mit den Größen der Objekte zusammenhängt.

Ich denke, dass ich Sie nicht schockieren würde, dass die Größe des Objekts vom Typ Angabe B (sizeof (B)), ist größer als der Typ A (sizeof (A)), da B hat alles, was A (durch Vererbung) hat mit einigen eigenen Sachen. Auf meinem Rechner sizeof(A) = 16 Bytes und sizeof(B) = 28 Bytes.

Wenn Sie also ein Array erstellen, ist der gesamte Speicherplatz, den das Array belegt, [element_count] * [size of the element] Bytes, was logisch erscheint. Aber wenn Sie ein Element aus einem Array nehmen müssen, muss es genau bestimmen, wo dieses Element im Speicher ist, in all dem Raum, den das Array belegt, und zwar durch Berechnung. Es geschieht so: [start of the array] + [index] * [size of element].

Und jetzt kommen wir an der Quelle des Problems. Sie versuchen cc->getA()[1] zu tun, aber, da cc, unter der Haube, ist BB*, so dass die Größe des AA::m_a Variable ist 2 * sizeof (B) (= 2 * 28 = 56 auf meiner Maschine, erste Objekte beginnen bei Offset 0 (0 * sizeof (B); Sekunde bei 28 Offset (1 * sizeof(B))) , aber seit cc->getA() wird behandelt als A*, and you are trying to fetch second element from the array (index 1), it tries to fetch the object from the offset of 1 * sizeof (A) `, die leider in der Mitte des reservierten Speicherplatzes für ein Objekt ist, und dennoch können beliebige Werte gedruckt werden/alles kann passieren - undefiniertes Verhalten wird aufgerufen.

Wie es zu beheben?Ich würde es beheben, indem die virtuellen Indizierung Betreiber Umsetzung statt GetA Methode auf Klassen AA/BB wie folgt:

class AA 
    { 
    public: 
     ... 
     virtual A& operator[] (int idx) 
      { 
      return m_a[idx]; 
      } 
     ... 
    }; 

class BB : public AA 
    { 
    public: 
     ... 
     virtual B& operator[] (int idx) 
      { 
      return dynamic_cast<B*>(m_a)[idx]; 
      } 
     ... 
    }; 

Aber dann würden Sie vorsichtig sein müssen sich die Betreiber auf dem Objekt aufzurufen, und nicht auf einen Zeiger auf Objekt:

std::cout << cc->operator[](1).idx[0] << "\n"; 
std::cout << cc->operator[](1).idx[1] << "\n"; 
std::cout << cc->operator[](1).idx[2] << "\n"; 
+0

Vielen Dank für die ausführliche Antwort. Ich hatte ähnliche Gedanken. Bezug nehmend auf 1. Wenn ich Drucke zum AA-Destruktor hinzufüge, zeigt es mir, dass es beim Löschen von bb aufgerufen wird, ohne es sogar als virtuell zu markieren. Zu 2. Gibt es andere Möglichkeiten, es zu lösen? Ich habe eine Side-API und ich wollte einige Überschreibungen wie B und BB hinzufügen, ohne die meisten API zu ändern, wenn es möglich ist. – user3147006

+0

@Oh, ja, das stimmt. Ich habe die Anordnung von Destruktoraufrufen durcheinander gebracht. Wird das momentan beheben. Ja, es gibt einen anderen Weg, es zu lösen, aber es ist in gewisser Weise das gleiche wie das, was Jotik vorgeschlagen hat, aber mit der Verwendung von 'dynamic_cast' anstelle von' static_cast', aber meiner Meinung nach ist diese Lösung sauberer. –

1

anzumerken, dass cc->getA() ist semantisch gleich cc->A::getA() (nicht cc->B::getA()) und gibt einen Zeiger auf A (statt B*).

Nun, da A ist die Unterklasse von B, aber das letztere enthält auch einige zusätzliche Felder, dann sizeof(B) > sizeof(A). Da cc->getA()[n] ist im Grunde *(cc->getA() + n) die Linie

cc->getA()[1].foo(); 

nicht das Gleiche wie:

A * const tmp = cc->getA(); 
A & tmp2 = *(tmp + 1); // sizeof(A) bytes past tmp 
tmp2.foo(); 

das aufgrund §5.7.6 undefiniertes Verhalten führt [expr.add] des C++ Standard in dem es heißt:

Für Addition oder Subtraktion, wenn die Ausdrücke P oder Q den Typ "Zeiger auf CV T" haben, wobei T und der Array-Elementtyp nicht ähnlich sind ([conv.qual]), das Verhalten ist nicht definiert. [Hinweis: Insbesondere kann ein Zeiger auf eine Basisklasse nicht für Zeigerarithmetik verwendet werden, wenn das Array Objekte eines abgeleiteten Klassentyps enthält. - Endnote]

Sie wahrscheinlich Verhalten ähnlich dem folgenden wollte:

A * const tmp = cc->getA(); 
A & tmp2 = *(static_cast<B *>(tmp) + 1); // sizeof(B) bytes past tmp 
tmp2.foo(); 

Dafür müssen Sie verwenden so etwas wie:

std::cout<<static_cast<B*>(cc->getA())[1].idx[0]<<"\n"; // prints 2 
std::cout<<static_cast<B*>(cc->getA())[1].idx[1]<<"\n"; // prints 3 
std::cout<<static_cast<B*>(cc->getA())[1].idx[2]<<"\n"; // prints 4 

static_cast<B*>(cc->getA())[1].foo(); // prints foo B 

Es ist jedoch besser, einen virtuellen A & operator[](std::size_t) Operator für AA zu implementieren und überschreiben Sie es in BB.

Verwandte Themen