delete mit Zeigern in std::list



  • Tag zusammen,

    mal wieder quält mich ein Problem. Folgende Klassendeklarationen seien angenommen:

    class A { virtual ~A()... };
    class B : public A { ... };
    

    Nun habe ich eine std::list<A*>, in der Zeiger auf B-Objekte liegen. Beim Aufräumen dieser Liste iteriere ich von begin() nach end() durch und rufe jeweils delete mit dem jeweiligen Zeiger.

    Allerdings hat's genau hier geknallt. Das erste Objekt wurde noch entfernt, alle restlichen allerdings nicht (Speicherzugriffsverletzung). Virtuelle Destruktoren sind überall an den richtigen Plätzen, daran liegt's nicht.

    Könnte es sein, dass std::list da irgendwie was mit der Größe der Objekte verbeutelt? Folgende Variante klappt nämlich:

    while( liste.size() ) {
      delete *liste.begin();
    }
    

    Der Vollständigkeit halber die Methode, bei der's knallt:

    std::list<A*>::iterator  iter( liste.begin() );
    std::list<A*>::iterator  iterend( liste.end() );
    
    for( ; iter != iterend; ++iter ) {
      delete *iter;
    }
    

    Den Container ändere ich während der for-Schleife auch nicht. Also ich lösche tatsächlich nur die Objekte, auf die die Zeiger im Container zeigen. Normal sollte hier std::list also auch gar nicht rumwurschteln. Deshalb kann ich mir so gar nicht erklären, warum die obige Methode funktioniert.

    Für Tipps wäre ich dankbar,
    Stefan.



  • Das geht bei mir problemlos:

    class A {
    public:
    	virtual ~A() { cout << "Ich werde zerstoert" << endl; }
    	virtual int elem() { return 0; }
    };
    
    class B : public A {
    	int el;
    public:
    	B() { }
    	B(int i) : el(i) { }
    	int elem() {
    		return el;
    	}
    };
    
    list<A*> liste;
    
    for(int i = 0; i < 10; i++) {
    	liste.push_back(new B(i));
    }
    
    for(list<A*>::iterator i = liste.begin(); i != liste.end(); i++) {
    	cout << (*i)->elem() << endl;
    	delete *i;
    }
    

    Hantierst du in den Klassen mit irgendwelchen Zeigern? Löschst du was? Geht bei Exceptions irgendwas schief? Geht bei Threads irgendwas schief? Geh mal mit dem Debugger und ein paar Breakpoints durch.



  • Kopierst du vielleicht den Container mal (evtl. löschst du den Speicher schon sonst irgendwo)? Führst du sonstige Operationen durch, die die Zeiger ungültig machen könnten?



  • Weder Exceptions, noch andere Threads, Kopien oder sonstwas. Wenn ich vor for-Schleife mit der Löschoperation eine Schleife einbauen, die z.B. eine Methode aller Objekte ruft, klappt das absolut problemlos. Nur sobald delete ins Spiel kommt, wird das Verhalten höchst komisch.

    Die Zeiger in der Liste bleiben auch von "push_back( new ... )" bis "delete ..." völlig unverändert, es wird nur dereferenziert. Deshalb kann ich's mir nicht erklären. Und die 2. Variante (1. Post) funktioniert ja ebenfalls, d.h. die Zeiger sind alle gültig.



  • Dass Folgendes klappt, beruht auf undefiniertem Verhalten. Das heisst weder, dass das gültiger Code ist noch dass es so immer funktioniert. Du hast nämlich eine Endlosschleife und löschst immer wieder den gleichen Speicherbereich.

    while( liste.size() ) { 
      delete *liste.begin(); 
    }
    

    Zeig doch mal ein minimales komplettes Beispiel, das für sich kompilierbar ist, aber das gegebene Verhalten immer noch repräsentiert.

    Alternativ kannst du im Debugger prüfen, ob die Adressen bei der Speicheranforderung mit denen bei der Freigabe übereinstimmen.



  • Nexus schrieb:

    Zeig doch mal ein minimales komplettes Beispiel, das für sich kompilierbar ist, aber das gegebene Verhalten immer noch repräsentiert.

    Das kann ich später liefern, bin gerade etwas kurz angebunden.

    Alternativ kannst du im Debugger prüfen, ob die Adressen bei der Speicheranforderung mit denen bei der Freigabe übereinstimmen.

    Genau das hatte ich mir mal ausgeben lassen. Und die Adressen, die über delete freigegeben werden sollen, stimmen *nicht* mit denen bei der Erzeugung überein. Aber wie kann das sein, wenn die Zeiger wirklich nicht modifiziert werden?



  • StefanBo schrieb:

    Genau das hatte ich mir mal ausgeben lassen. Und die Adressen, die über delete freigegeben werden sollen, stimmen *nicht* mit denen bei der Erzeugung überein. Aber wie kann das sein, wenn die Zeiger wirklich nicht modifiziert werden?

    Gar nicht -> die Zeiger werden modifiziert. Die Frage ist, ob du das absichtlich machst. Eventuell hast du sonst irgendwo noch undefiniertes Verhalten drin...

    Deshalb wäre ein kleines Beispiel noch gut. Wobei ich allerdings befürchte, dass das nicht allzu einfach wird, wenn der Fehler beibehalten werden soll. Was du versuchen könntest, ist den Code ständig zu kürzen und schauen, ob das Problem immer noch auftritt.

    Ich würde dir übrigens raten, mit der Zeit auf sicherere Container, z.B. Boosts Pointer-Container, umzusteigen. Dann hast du diese Probleme nicht und musst dich auch nicht um die Freigabe kümmern. Oder du benutzt Smart Pointer (wobei deren Overhead oft nicht benötigt wird). Aber rohe, besitzende Zeiger in Containern können verdammt gefährlich sein, auch bezüglich Memory Leaks und solcher Sachen. Du musst auf diese Weise auch ständig den Speicher manuell verwalten (Freigabe, Kopieren, etc.), eine wichtige Aufgabe kann dir der Container also nicht mehr abnehmen.



  • Rohe Zeiger nutze ich tatsächlich sehr selten. Momentan probiere ich allerdings die ext. Abhängigkeiten so gering wie möglich zu halten. Wobei -- die Boost-Pointer-Container müssten ja eigentlich nur Header sein, von daher böte sich das an.

    Dennoch möchte man ja wissen, woran es scheitert. 🙂

    Um den Fehler zu finden, habe ich schon an vielen Stellen gekürzt, Ausgaben hinzugefügt, den Debugger bemüht etc. Bisher leider ohne Erfolg.

    Zum unabsichtlichen Ändern: Es muss ja im Grunde so sein, da die Standard-Lib-Implementierungen gewiss bugfrei sind. Nur ist's halt schwierig herauszufinden, wenn man außer

    - Objekte erzeugen und deren Zeiger sichern,
    - drüber iterieren und Methoden aufrufen
    - und am Schluss wieder freigeben

    nichts tut. Aber wer weiß, womöglich knallt's an einer gänzlich anderen Stelle. Ich werde nochmals den Debugger anschmeißen und die Liste überwachen.

    Ich danke dir (und den anderen) auf jeden Fall schon mal für die Hilfe. Und sag mal, kenne ich dich zufällig aus dem SMFL-Forum? 🙂



  • StefanBo schrieb:

    Dennoch möchte man ja wissen, woran es scheitert. 🙂

    Hehe, das kenne ich nur zu gut. 😉

    StefanBo schrieb:

    Ich werde nochmals den Debugger anschmeißen und die Liste überwachen.

    Ja, so sollte der Fehler schon zu finden sein. Möglichst nach jeder kleinen Operation wieder die Zeiger prüfen, nicht denken "hier passiert sicher nichts". Oder im ersten Durchgang nur an zwei, drei Orten prüfen und dann immer mehr einschränken. Blöd ist das nur, wenn das Verhalten bei jedem Programmdurchlauf wieder ein bisschen anders ist, was unter Umständen gut sein kann.

    Wenns nicht zuviel Code ist, kannst du den auch mal hier posten. Und sonst halt wie gesagt ein wenig kürzen, das sollte schon möglich sein...

    StefanBo schrieb:

    Ich danke dir (und den anderen) auf jeden Fall schon mal für die Hilfe. Und sag mal, kenne ich dich zufällig aus dem SMFL-Forum? 🙂

    Kein Problem. Hmm, kann gut sein, ich bin auch im SFML-Forum angemeldet. Wie heisst du denn dort?



  • Tatsächlich ist's mir gelungen den Fehler finden zu können. Folgendes Passiert:
    - Elternobjekt, welches die Liste enthält, zerstört die Kindselemente.
    - Jedes Kindselement löst sich im Destruktor vom Elternobjekt und ruft hierfür eine Callback.
    - In diesem Callback wird dann das Kindselement aus der Liste des Elternelements entfernt.
    - Beim ++iter kann man sich dann vorstellen, was passiert...

    Also ein großes Sorry an dieser Stelle für die Verwirrung. Da habe ich den roten Faden nicht weit genug verfolgt..

    Im SFML-Forum bin ich als "Tank" unterwegs.



  • Gut, dass du es lösen konntest! Solche Fehler können echt sehr mühsam sein.

    Und sind wir uns tatsächlich schon im SFML-Forum begegnet. 🙂


Log in to reply