Container-Daten nach clear() noch im Iterator vorhanden



  • Hallo,

    mich wundert, daß im folgenden Konstrukt die Daten noch im Speicher sind.
    Erhält ein Iterator beim konstruieren alle Daten des Containers?

    vector<int> v;
        // vector<int>::iterator Iter=v.begin(); // führt natürlich zum Absturz
        v.push_back(1);
        v.push_back(2);
    
        vector<int>::iterator Iter=v.begin();
    
        v.clear();      // Jetzt sollten alle Daten aus dem Speicher sein
    
        Iter++;   
    
        cout << *Iter << endl;      // Ausgabe: "2". Also muß noch eine Kopie im Speicher liegen
                                    // Iter wurde erhöht, muss beim konstr. also alle daten haben
    
        cout << v.size() << endl;   // Ausgabe: "0". So wie es sein sollte
    


  • Der Speicher wird nicht leer gemacht oder auf 0 gesetzt. Der Vektor wird nur wieder auf den Zustand "leer" gesetzt.
    Was Du da machst, ist nicht erlaubt, und der Zustand ist undefiniert.



  • XHansWurstX schrieb:

    mich wundert, daß im folgenden Konstrukt die Daten noch im Speicher sind.

    Clear löscht nicht zwangsweise die Elementinhalte, dies bekommst du hin in dem du den vector entsprechend verkleinerst. Und ein Iteratorzugriff nach irgendeiner Form einer Änderung des Vectors ist eh undefiniert.

    cu André



  • Tachyon schrieb:

    Was Du da machst, ist nicht erlaubt, und der Zustand ist undefiniert.

    Ich denke mal, Du meinst die Zeilen nach dem clear() 😉



  • XHansWurstX schrieb:

    Erhält ein Iterator beim konstruieren alle Daten des Containers?

    Wie sieht's damit aus? Scheint ja so zu sein...



  • XHansWurstX schrieb:

    Tachyon schrieb:

    Was Du da machst, ist nicht erlaubt, und der Zustand ist undefiniert.

    Ich denke mal, Du meinst die Zeilen nach dem clear() 😉

    Die Operationen, die Du auf dem Iterator durchführst. "Zustand" ist auch nicht das richtige Wort. Man sollte eher sagen, dass der Iterator ungültig ist.
    Es gibt aber seitens des Vektors oder des Iterators keine Checks auf Gültigkeit. Daher "geht" das, jedoch ist das Programmverhalten an der Stelle undefiniert.



  • Btw ist mir klar, daß ich solche Zeilen in keinen Code einfüge 😉

    Es ist mir nur zufällig aufgefallen...



  • XHansWurstX schrieb:

    XHansWurstX schrieb:

    Erhält ein Iterator beim konstruieren alle Daten des Containers?

    Wie sieht's damit aus? Scheint ja so zu sein...

    Ein Iterator enthält keinerlei Daten sondern nur das für den Elementzugriff wichtige. Im einfachsten Fall kann ein Iterator ein dummer Zeiger auf den Speicherbereich sein (Im Falle des std::vector sogar nicht unwahrscheinlich).

    Stell dir die Situation wie Folgt vor:

    std::vector<int> intContainer;
    
    // Ein vector wird erstellt, dieser wird auf eine Initialgröße, sagen wir mal 3
    // gesetzt. Sprich: Platz für 3 ints, size wird mit 0 angegeben (Der Platz ist
    // aber reserviert und enthält auch schon im initialzustand Werte:
    // size     : 0
    // capacity : 3
    // Daten    : [0, 0, 0]
    
    intContainer.push_back(4);
    intContainer.push_back(5);
    intContainer.push_back(6);
    
    // size     : 3
    // capacity : 3
    // Daten    : [4, 5, 6]
    
    std::vector<int>::iterator it = intContainer.begin(); 
    
    // Der Iterator verweist auf den aktuellen Beginn. In der Regel ist das beim
    // vector einfach ein Zeiger auf das erste Element
    
    intContainer.push_back(7);
    
    // Nun reicht der Speicherbereich nicht mehr aus, muss also neualloziert
    // werden (z.B. mit der doppelten capazity)
    // size     : 4
    // capacity : 6
    // Daten    : [4, 5, 6, 7, 0, 0] NEUER SPEICHERBEREICH!
    
    int a = (*begin); // Fehler!
    
    // Der Iterator ist ungültig, er zeigt weiterhin auf den alten Speicherbereich
    // ...
    
    begin = intContainer.begin(); //Dieser wäre gültig
    intContainer.clear();
    
    // clear setzt zwar den "size" auf 0. Löscht aber nicht zwangsweise die
    // Elemente...
    // size     : 0
    // capacity : 6
    // Daten    : [4, 5, 6, 7, 0, 0]
    
    // Nun ist begin auch wieder ungültig, er mag auf die noch vorhandenen
    // Elemente zeigen, aber vermutlich ist begin nun gleich intContainer.end()
    

    cu André



  • ein clear() im vector heißt, dass der Destruktor der entsprechenden Elemente aufgerufen wird und dass damit, semantisch gesehen, die entsprechenden Elemente nicht mehr existieren. Daher sind auch die Iteratoren auf die Elemente ungültig.
    Soweit zur Theorie.
    Was in der Praxis geschieht, ist durch den Standrad nicht festgelegt. Iteratoren werden intern meist mit Zeigern implementiert. Man könnte also dafür sorgen, dass sie auf irgendein gültiges Element zeigen oder die Zeiger auf 0 setzen um anzuzeigen dass sie ungültig sind. Dazu müsste aber der Container jeden iterator kennen der auf eines seiner Elemente verweist, und die Änderungen der iteratoren nehmen auch Zeit in Anspruch. Nach der C++-Philosophie "don't pay for what you don't need" wird das alles sein gelassen, damit verweisen die Pointer weiterhin auf den Speicherbereich wo vorher mal das Element war. Da weiterhin die Destruktoren der Elemente nicht unbedingt was tun müssen, heißt das, dass meistens (bzw. fast immer) nach der Zerstörung eines Objektes dessen Bitmuster weiterhin im Speicher stehn bleibt. Fazit: der ungültige Iterator greift über einen Pointer auf T auf Speicher zu, der "zufälligerweise" das Bitmuster eines T enthält und es sieht so aus als ob da tatsächlich noch ein Objekt vom Typ T existiert. Der Speicher gehört weiterhin dem vector (der gibt ihn nicht frei), ist für den aber einfach uninitialisierter Speicher. Du könntest jetzt noch einen Schritt weiter gehen:

    std::vector<T>::iterator foo(T t1, T t2)
    {
      std::vector<T> bar;
      bar.push_back( t1 );
      bar.push_back( t2 ); //zwei Objekte einfügen
      std::vector<T>::iterator it = bar.begin();
      ++it; //zeigt aufs zweite Element
      bar.clear();  //it ist ungültig, der Speicher gehört noch bar
      return it;
    }
    
    int main()
    {
      T t1("bla");
      T t2("blubb");
      std::vector<T>::iterator testIter = foo(t1, t2);
      T t = *testIter; 
    }
    

    testIter ist ungültig und zeigt sogar auf Speicher, der niemandem gehört -der vector bar hat ihn am Ende von foo() im Destruktor freigegeben. Trotzdem sind die chancen nicht schlecht, dass t problemlos initialisiert wurde mit den Werten von t2, weil die immernoch in dem Speicher standen auch nach der Freigabe.

    Aber: all das ist nicht garantiert! Die Implementation darf sehr wohl den internen Pointer des ungültigen Iterators verändern, oder nach der Zerstörung der vector-Elemente deren Speicher auf 0 setzen (z.B. zu Debugzwecken), oder der Speicher wird nach der Freigabe ans Betriebssystem zurückgegeben und anderweitig verwendet, oder...

    Zusammengefasst: das Arbeiten mit ungültigen iteratoren erzeugt undefiniertes verhalten. "don't try this at home" 😉



  • Danke, sehr informative/ interessante Antworten.


Anmelden zum Antworten