2016-04-22 2 views
2

Ich habe eine sehr eigenartige Situation hier ... Ich habe einige alte C++ - Code (pre-C++ 11) geerbt, die lange, komplexe und die Hälfte davon ist mit geschrieben C und die andere Hälfte mit C-mit-Klassen-Mentalität (dh Klassen mit mehr Datenmitgliedern, nicht zu viele Klassenmethoden, direkte Manipulation von Datenmitgliedern ... definitiv benötigt dieses Projekt ein Refactoring, an dem ich gerade arbeite jetzt). Aber der Code deckt einige Probleme auf, die ich rätselhaft finde.Kann eine C++ - Klasseninstanziierung ihre Größe während der Laufzeit ändern

Für den Anfang wollen wir die folgenden sehr einfach nehmen (frei Fehler) Situation:

#include <iostream> 

static int c = 0; 

struct Bar 
{ 
    Bar() : base_id(++c) { std::cout << "Bar "<< base_id << std::endl;} 
    int base_id; 
}; 

struct Foo : public Bar 
{ 
    Foo() : x(c) { std::cout << "Foo "<< x << std::endl;} 
    int x; 
}; 

int main() 
{ 
    Bar* b = new Foo[200]; 
    Foo *p; 

    for(p = (Foo*)b; p - (Foo*)b < 200; p ++) 
    { 
     std::cout << p->base_id << " " << ((Foo*)p)->x 
     << " p-b=" << (unsigned)(p-(Foo*)b) << std::endl; 
    } 


    delete[] b; 
} 

oder Textversion: wir eine Reihe von Basisklassenobjekte über eine new Derived erstellen. So weit, so gut, das ist, was die große komplexe Anwendung tut (dort sind die Klassen hierarchischer und die Konstruktoren mehr), aber die globale statische Variable (c) ist auch in der großen komplexen Anwendung vorhanden, sie verwendet sie als eindeutiges Objekt Identifikatoren. Dann beginnt große komplexe Anwendung zu arbeiten, usw. ....

Dann zu einem bestimmten Zeitpunkt durchlaufen wir das Array von Objekten, einige Arbeit zu tun. Die Iteration sieht genauso aus wie die, die ich hier geschrieben habe, in einer for-Schleife mit erschöpfender Zeigerarithmetik. Mein Beispiel hier druckt nur die Objekt-ID (m) in der großen komplexen Anwendung, die mehr getan wird.

Aber irgendwo in der großen komplexen Anwendung passiert etwas Magie ... Irgendwann nach der Hälfte der Liste sind die Objekte (erhalten über die Zeiger) nicht mehr gültig, ihre base_id s zeigen Daten, die für mich schön sind viel sieht aus wie Zeiger und andere Datenmitglieder Werte. Wenn ich jedoch die Array-Mitglieder an dem spezifischen Index überprüfe, ist der Wert dort gültig (dh: Objekt-IDs werden korrekt erhöht).

Ich habe auch die folgenden:

  • Es scheint kein Problem Speicher Korruption zu sein. Valgrinds und Clang's Gedächtnis-Desinfektionsmittel zeigen keine übertriebenen Dinge.
  • Alle Objekte werden ordnungsgemäß erstellt und zerstört, keine Speicherverluste.
  • Der einzige Ort, an dem diese Verrücktheit ist

    So ... Ich kam zu einem Abschluss in dieser Schleife über

passiert:

  • jemand irgendwo modifiziert einige Array-Werte tatsächlich verweisen auf verschiedene Arten von Objekt (alle von ihnen sind abgeleitet von Bar (e ... einige anders benannte Basisklasse) so möglicherweise muss ich mehr in den Code zu finden, um dies zu finden.Dies ist eine riesige Codebasis, gibt es buchstäblich Tausende von Referenzen für Dieses Array und davor möchte ich fragen:

(Und hier kommt die Frage :)

ich nicht bewusst bin, dass ein C++ Objekt seine Größe während der Laufzeit ändern kann (wenn es möglich ist, es mit uns bitte teilen), so neben der möglichen Ausgabe, die ich habe Hat jemand in der Community irgendwelche Ideen gefunden, warum sich diese Zeigerarithmetik so seltsam verhält?

(ja, das Projekt wird in die richtige C++ 11 mit Standardcontainern konvertiert, etc ...aber jetzt bin ich nur daran interessiert in diesem speziellen Thema)

+4

Die Größe des Objekts wird zur Kompilierzeit berechnet. Es kann nicht in der Laufzeit geändert werden. – teivaz

+2

"So weit, so gut" - äh, nein, du bist schon ziemlich geschraubt. Siehe hier: http://stackoverflow.com/questions/6171814/why-is-it-undefined-behavior-to-delete-an-array-of-edived-objects-via-a-base – Mat

+0

außer den Klassen sind streng Standard-Layout, (Foo *) b wird nicht unbedingt den gleichen Wert (Adresse) wie b. Könnte das relevant sein? –

Antwort

2

Ihr Problem hier ist:

Bar* b = new Foo[200]; 

b verweist auf eine Bar subobject im ersten Element des Foo Array, ist aber nicht verwendbar als eine Möglichkeit, um auf das Array zuzugreifen; die Größe ist falsch. Es wird immer wieder auf einen Foo Zeiger geworfen, der funktioniert, ist aber anfällig für Fehler. (Es wird noch schlimmer, angesichts der Mehrfachvererbung, wenn die Bar Subobjekt nicht einmal an der gleichen Adresse wie das Objekt Foo ist es Teil.)

Der Code, den Sie geschrieben vorsichtig zu werfen immer b zu einem Foo* vor der Zeigerarithmetik. (Auch zu vorsichtig: ((Foo*)p)->x könnte nur p->x sein.) Aber wenn jemand vergisst, dies zu tun (z. B. b[i] oder b+i oder (Foo*)(b+i) ...), wird es genau zu dem Verhalten führen, das Sie beschreiben. Oder vielleicht, mit all dies Downcasting geht, jemand downcasts die Bar* zu einem Baz*, wo Baz auch von Bar erbt, aber eine andere Größe von Foo hat. Das würde auch Felder auf wackelige Weise überschreiben.

Auch delete[] bis undefined behavior. Also ist es nicht außerhalb des Reichs der Möglichkeiten für den Compiler zu schließen, dass b wirklich auf Bar Instanzen zeigt, und elide oder vermasseln Casts zu Foo* -Compiler tun diese Art von Dingen in diesen Tagen.

Alles in allem ist Bar* b = new Foo[200]; nicht praktikabel. Verwenden Sie

Foo* fp = new Foo[200]; 

stattdessen. Wenn aus irgendeinem Grund, benötigen Sie einen Bar*, können Sie es mit

Bar* b = fp; 

folgen Aber es ist nicht klar, warum diese benötigen; Sie können fp immer verwenden, wenn Bar* erforderlich ist.

+0

Beachten Sie jedoch, dass 'Bar * b = fp;' Leute dazu verleiten wird, Dinge wie 'b [i]' zu tun, und Sie wieder da sind, wo Sie angefangen haben. Besser wäre "Bar & b = fp [0];". –