seltsame Variblenänderungen am Ende von Konstruktoraufruf



  • Zu dem Zeitpunkt, an dem der Zeiger auf freigegebenen Speicher zeigt, ist die Instanz des Objektes, auf das er eigentlich zeigen sollte, laut Debugger noch vorhanden!


  • Mod

    Knobbers schrieb:

    Zu dem Zeitpunkt, an dem der Zeiger auf freigegebenen Speicher zeigt, ist die Instanz des Objektes, auf das er eigentlich zeigen sollte, laut Debugger noch vorhanden!

    Kein Debugger kann dir sagen, ob an einer bestimmten Speicherstelle ein bestimmtes Objekt existiert oder nicht.
    Alles was dir mitgeteilt wird ist: falls an der Stelle ein bestimmtes Objekt existiert, dann hat es den angezeigten Inhalt.



  • Ok, vielen Dank euch allen für eure Hilfe!
    Ich werd mal sehn, ob ich meinen Fehler finde.



  • Hi, ich konnte leider den Fehler immer noch nicht finden.
    Ich hab jetzt folgenden Versuch gemacht:

    [code]
    
    schlacht::schlacht(...){
         (...)
         //die Adresse einer Instanz der Klasse "einheit" zuweisen
         test_pointer_auf_einheit = sArmee->getEinheit(0); 
    
         //die Adresse einer Instanz der Klasse "feld" zuweisen
         test_pointer_auf_einheit->test_pointer_auf_feld = sMap.getFeld(0,0);
    
         //einen bool-Member der Instanz ändern
         sMap.getFeld(0,0)->belegt = false;
    
    }
    
    //diese Funktion wird kurz nach dem Konstruktor aufgerufen
    void schlacht::aktionenJeFrame(){
        //einheit-zeiger funktioniert, test1 bekommt den richtigen Wert
        int test1 = test_pointer_auf_einheit->pos.x;
    
        //die Instanz von "feld" existiert noch, test2 wird false
        bool test2 = sMap.getFeld(0,0)->belegt;
    
        // test3 wird true, der Zeiger funktioniert nicht richtig
        bool test3 = test_pointer_auf_einheit->test_pointer_auf_feld->belegt;
    
    weiteren Member am Ende des Zeigers werden von MSVS als FEEEFEEE angezeigt
    

    Die Instanz der Feld-Klasse, auf die mein Zeiger zeigt, ist also noch vorhanden (Zeile 20). Und trotzdem kann ich sie nicht über den Zeiger ansteuern, der von meiner Einheit-Instanz gespeichert wird (Zeile 23). Habt ihr noch ein paar Ideen auf Lager? Ich weiß echt nicht mehr weiter.



  • Was machst du im schlacht-Konstruktor vor dem Zuweisen der Zeiger?
    Wo kommen sMap/sArmee her?
    Die Punkte im Konstruktor machen mich stutzig: Steht das wirklich da? Und wenn nicht: hast du mehrere Konstruktoren und rufst sicher den richtigen auf?
    Kannst du nicht einfach dein Projekt in ein zip packen und um Download anbieten? Oder wenigstens ein minimales Beispiel, das man nachvollziehen kann. Mit so mini-Infos kann man nur raten...



  • @Rhododendronsau
    Anstelle der Punkte steht einen Haufen Zeug. sMap und sArmee sind Klassen und Member von schlacht. Und ja, der richtige Konstruktor wird aufgerufen, ich debugge mich ja Schritt für Schritt durch die geposteten Zeilen.

    Der Punkt ist doch, dass ich UNMITTELBAR nach Beenden des Konstruktors nicht über den Zeiger "test_zeiger_auf_feld" auf das gewünschte Feld zugreifen kann (per Debugger oder per Code), OBWOHL das Feld exisitert (Zeile 22) sowie die Einheit, welche diesen Zeiger testhalber speichert (Zeile 19).

    Zwischen denn letzten Zeilen des Konstruktors und dem Funktionsaufruf von "aktionenJeFrame" ist sonst kein Code.



  • Was genau liefert denn "sArmee->getEinheit(0)" bzw. "sMap.getFeld(0,0)" zurück?
    Zeig mal den Code - ich tippe auf einen Zeiger (Adresse) einer lokalen Variable (peng!)...



  • Th69 schrieb:

    Was genau liefert denn "sArmee->getEinheit(0)" bzw. "sMap.getFeld(0,0)" zurück?

    Ich versteh nicht. Willst du den Wert wissen? 0x1235ef4c? Adressen halt.

    Th69 schrieb:

    lokalen Variable

    Was soll lokal sein? Das Feld auf das "test_pointer_auf_feld" zeigt? Wenn das so wäre könnte ich ja nicht wie in Zeile 22 meines Codes drauf zugreifen.



  • Knobbers schrieb:

    Was soll lokal sein? Das Feld auf das "test_pointer_auf_feld" zeigt? Wenn das so wäre könnte ich ja nicht wie in Zeile 22 meines Codes drauf zugreifen.

    Genau, das Feld. Wenn dein sMap() intern ein Array speichert, und du dann sowas machst:

    Feld f = Array[0][0];
    return &f;
    

    Dann liegt in deinem test_pointer eine Adresse auf ein temporäres Objekt-Bumm!
    Und darauf zugreifen kannst du natürlich - sowas nennt sich "undefiniertes Verhalten". Es kann gut gehen, aber auch nicht, Du kannst auch plötzlich nackt in der U-Bahn stehen...
    Dass die Werte plötzlich nicht mehr stimmen deutet sehr stark auf undefiniertes Verhalten hin...



  • @Siebenweger

    Ich mach glaub ich sowas ähnliches, wie du vermutet hast. Ich speicher die Felder in sMap in einem Vektor.

    for (int i = 0; i < 100; i++){
         for (unsigned int j = 0; j < 100; j++){
    	     felder.push_back(feld(i,j)); //std::vector<feld> felder
         }
    }
    

    Wenn ich testhalber Felder außerhalb des Vektors anlege und ihre Adresse verteile, dann funktioniert es. Aber wieso funktioniert es nicht mit dem Vektor? Dieser ist ja ein fester sMap-Member. Der dürfte ja nicht einfach plötzlich seinen Inhalt freigeben. Hat das irgendwas mit seiner variablen Größe zu tun oder so?

    Siebenweger schrieb:

    Du kannst auch plötzlich nackt in der U-Bahn stehen...

    😃 Davor hat mich kein C++-Tutorial gewarnt!!!



  • Hallo,
    mich hat das Problem auch weiterhin verfolgt. Ich hab jetz ein anschauliches Codebeispiel programmiert:

    #include <iostream>
    #include <vector>
    class A{
         public:
         A(std::vector<int> *Zahlen);
         A();
         std::vector<int> *zahlen;
    };
    A::A(std::vector<int> *Zahlen){
         zahlen = Zahlen;
    }
    class B{
         public:
         B();
         std::vector<int> zahlen;
         A myA;
    };
    B::B(){
         for (int i = 0; i < 100; i++){
    	  zahlen.push_back(i);
         }
         myA = A(&zahlen);
         std::cout << myA.zahlen->at(99) << std::endl;
    }
    A::A(){}
    B myB;
    int main( int argc, char* args[] ){
         myB = B();
         myB.zahlen.at(99) = 17;
         std::cout << myB.zahlen.at(99) << std::endl;
         //hier kommt der Fehler
         std::cout << myB.myA.zahlen->at(99) << std::endl;
         return 0;    
    }
    

    Weiß vielleicht jemand, warum das zu einem Fehler führt?



  • Das kann ich dir sagen; es ist aber etwas verworren.

    Grundlegend ist dein Denkfehler, dass Klassen in C++ keine Referenztypen wie in Java oder C# sind. Dein Problem sind

    myA = A(&zahlen);
    

    und

    myB = B();
    

    - konkret ausgelöst wird der Fehler von letzterem.

    myB ist zu diesem Zeitpunkt bereits ein vollständig initialisiertes Objekt vom Typ B, also wird hier kein Konstruktor für myB aufgerufen, sondern ein temporäres Objekt vom Typ B erstellt und dann in B::operator= geworfen. Da du keinen eigenen operator= geschrieben hast, wird der vom Compiler generierte benutzt, und dieser kopiert schlicht die Member des temporären Objektes nach myB, indem er ihre respektiven operator= benutzt.

    Da A ebenfalls keinen explizit geschriebenen operator= hat, wird hier ebenfalls der vom Compiler generierte benutzt, und dieser kopiert dann den Zeiger aus dem myA-Objekt im temporären B-Objekt aus main in das myA-Objekt in myB; dieser Zeiger zeigt auf den Vektor im temporären B-Objekt.

    Wenn also nach getaner Arbeit das temporäre B-Objekt zerstört wird, zeigt der Zeiger myB.myA.zeiger dorthin, wo einmal der Vektor des temporären Objektes war und jetzt nicht mehr ist. Er ist mithin ungültig, und alle Versuche, durch ihn auf etwas zuzugreifen, erzeugen undefiniertes Verhalten.



  • @seldon
    Hey danke, das ergibt Sinn. Könnte ich das Problem umgehen, indem ich einfach eine Art Initialisierungsfunktion schreibe und diese anstatt des Konstruktors aufrufe?

    myB.ini(int a, char b);
    

    Oder wäre das mit dem =-Operator eleganter? Müsst ich halt erst rausfinden, wie das geht.



  • Wirklich elegant ist diese ganze Verschränkungskiste nicht. Das Problem für eine elegante Lösung ist, dass du die Regel der großen drei nicht einhalten kannst, weil Klasse A nicht genug weiß, um sich zu kopieren - konkret kennt A in operator= die Adresse des Zahlen-Vektors im neuen B-Objekt, das es besitzt, nicht.

    Ich würde darüber nachdenken, ob sich der Sachverhalt nicht einfacher modellieren lässt; diese Frage kann ich aber nicht beantworten, weil ich nicht weiß, was du eigentlich vorhast. Ist das nicht möglich, so solltest du A auf jeden Fall nicht kopierbar machen, also den Kopierkonstruktor sowie operator= verstecken:

    class A {
    ...
    
    private:
      // Eine Implementation ist nicht notwendig; sie sollen ja nicht benutzt werden.
      A(A const &);
      A& operator=(A const &);
    };
    

    Wobei es eigentlich schöner ist, dafür eine Klasse zu benutzen:

    class noncopyable {
    protected:
      noncopyable() { }
    
    private:
      noncopyable(noncopyable const &);
      noncopyable &operator=(noncopyable const &);
    };
    
    class A : private noncopyable {
      ...
    };
    

    Diese Klasse ist schamlos aus Boost geklaut; wenn du Boost also schon benutzt, musst du das nicht selbst vorhalten, sondern kannst

    #include <boost/noncopyable.hpp>
    
    class A : private boost::noncopyable {
      ...
    };
    

    schreiben.

    Das hat den Effekt, dass die Kopierfunktionen für Klassen, die Objekte des Typs A enthalten, nicht mehr automatisch generiert werden und du auf solche Fehler vom Compiler hingewiesen wirst. Dann musst du für die Typen, die A verwenden, entweder Kopiersemantik auch verbieten oder sie den Umständen entsprechend von Hand definieren. In diesem konkreten Fall muss der Zeiger in A nie verändert werden, weil es dem selben Objekt gehört, in das es zeigt, also ginge das beispielsweise so:

    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    #include <boost/noncopyable.hpp>
    
    class A : private boost::noncopyable {
    public:
      A(std::vector<int> *zahlen_p);
    
      std::vector<int> *zahlen;
    };
    
    A::A(std::vector<int> *zahlen_p)
      : zahlen(zahlen_p) { }
    
    class B{
    public:
      B();
      B(B const &other);
    
      B &operator=(B const &other);
    
      void swap(B &other);
    
      std::vector<int> zahlen;
      A myA;
    };
    
    B::B(B const &other) : zahlen(other.zahlen), myA(&zahlen) { }
    B &B::operator=(B const &other) {
      // Das hier ist als copy-and-swap-Idiom bekannt und in Anwesenheit eines
      // non-throwing swap die einfachste Möglichkeit, die Regel der großen Drei
      // exceptionsicher umzusetzen.
      B tmp(other);
      tmp.swap(*this);
      return *this;
    }
    
    void B::swap(B &other) {
      using std::swap;
      swap(zahlen, other.zahlen);
    }
    
    B::B() : myA(&zahlen) {
      for (int i = 0; i < 100; i++){
        zahlen.push_back(i);
      }
    
      std::cout << myA.zahlen->at(99) << std::endl;
    }
    
    int main(){
      B myB;
    
      myB.zahlen.at(99) = 17;
      std::cout << myB.zahlen.at(99) << std::endl;
      std::cout << myB.myA.zahlen->at(99) << std::endl;
    
      myB = B(); // Zuweisung eines temporären Objekts
      std::cout << myB.zahlen.at(99) << std::endl;
      std::cout << myB.myA.zahlen->at(99) << std::endl;
    }
    

    In der Realität muss man natürlich die Implementationsdetails von A und B in einer Weise verstecken, die es verhindern, dass die Zeiger von außen verändert werden, sonst kann man sich später auf überhaupt nichts verlassen. Die Arbeit habe ich mir hier nicht gemacht.



  • Ja, ich habs noch nicht so ganz raus, wie man sein Programm mit Klassen aufbaut. Jetzt kann ich aber auf jeden Fall mal weitermachen! Danke vielmals für deine Mühen.


Anmelden zum Antworten