Nach delete Zeiger benutzbar?



  • Hallo,

    ich habe eine map<void*, Foo>, in der Heap Zeiger speichere. Nun will ich einen dieser Speicherbereiche freigeben und auch den Zeigereintrag in der Map löschen.

    int* p = new ...
    map[p] = ...
    
    delete p;
    MapIterator it = map.find(p);
    if(it != map.end())    
      map.erase(it);
    

    Ist der Code ok oder nicht? Klar, nach dem delete zeigt p auf Schrott, aber der Inhalt von p wird durch das delete ja nicht geändert und den Eintrag gibt es ja noch immer in der map und ich dereferenziere p nirgend mehr. Ist das also ok?



  • Hallo,

    Ich finde es ein bischen merkwürdig, dass du den Zeiger schon löschst bevor du prüfst ob der überhaupt in der map drin ist.



  • Also für mich sieht es in Ordnung aus, aber ich bin nicht 100% sicher, ob vielleicht eine Spezialisierung von less<> für Zeiger den Wert doch dereferenziert. Um ganz sicher zu gehen, kannst du auch erst den map-Eintrag löschen und danach den Zeiger delete'n.

    (oder die bessere Alternative: nimm keine nackten Zeiger, sondern std::unique_ptr<> ;))


  • Mod

    deletewilliger schrieb:

    Ist der Code ok oder nicht?

    undefiniert.

    Im Allgemeinen sollte die Freigabe von Resources niemals passieren, bevor nicht alle internen Verweise darauf gelöscht wurden.



  • Das Programm löst bei einem Kumpel eine Acess Violation. aus, bei mir aber nicht. Kann dieser Code eine Access Violation auslösen?


  • Mod

    camper schrieb:

    deletewilliger schrieb:

    Ist der Code ok oder nicht?

    undefiniert.

    Wieso? Es gibt da ganz strenggenommen die Regel, dass die Zeiger die in der map verglichen werden, hier nicht aus dem gleichen Array stammen. Aber ich glaube man darf davon ausgehen, dass er kein System mit segmentiertem Speicher benutzt.

    Das Programm löst bei einem Kumpel eine Acess Violation. aus, bei mir aber nicht. Kann dieser Code eine Access Violation auslösen?

    Sofern dein Kumpel keinen Mikrocontroller oder MS_DOS benutzt hat, eigentlich nicht. Natürlich hast du jede Menge in deinem Beispiel weggelassen. Und da du da ohne ersichtliche Not mit rohen Zeigern hantierst, kann ich mir gut vorstellen, dass du da was falsch machst.



  • SeppJ schrieb:

    camper schrieb:

    deletewilliger schrieb:

    Ist der Code ok oder nicht?

    undefiniert.

    Wieso? Es gibt da ganz strenggenommen die Regel, dass die Zeiger die in der map verglichen werden, hier nicht aus dem gleichen Array stammen.

    Aber

    C++03 20.3.3/8 schrieb:

    For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.

    Und sonst sehe ich auch kein Problem in diesem Code.



  • Ich verstehe nicht, wie das bei ihm eine Access Violation gibt und bei mir nicht (haben beide Windows 7, selben Compiler etc.)

    Ich sehe auch kein Problem mit dem Code. Das Keyword delete ändert ja nicht den Inhalt von p und die Adresse ist nach dem delete ja noch immer in der map. Also sollte das map::find() problemlos gehen (map::find() wird ja nicht dereferenzieren sondern nur den Inhalt von p mit den anderen Elementen vergleichen)

    😕



  • Habt ihr auch beide mit den selben Projekt-Einstellungen übersetzt? Womöglich ist das Programm im Debug-Modus etwas toleranter als im Release-Modus.

    PS: Ansonsten: std::map<> verwendet normalerweise std::less<> als Vergleichskriterium - und da gilt das, was volkard aus dem Standard zitiert hat.


  • Mod

    deletewilliger schrieb:

    Ich verstehe nicht, wie das bei ihm eine Access Violation gibt und bei mir nicht (haben beide Windows 7, selben Compiler etc.)

    Ich sehe auch kein Problem mit dem Code. Das Keyword delete ändert ja nicht den Inhalt von p und die Adresse ist nach dem delete ja noch immer in der map. Also sollte das map::find() problemlos gehen (map::find() wird ja nicht dereferenzieren sondern nur den Inhalt von p mit den anderen Elementen vergleichen)

    😕

    Dann zeig mal den vollständigen Code der den Fehler wirft. Oder besser noch ein minimales Beispiel, welches gerade noch einen Fehler erzeugt, aber du nicht verstehst, warum.



  • Vielleicht liegt es gar nicht an diesem Code.

    int* p = new ...
    map[p] = ...
    
    int* arr=new int[100];
    arr[100]=3;//dieser fehlerhafte Code
    delete[] arr;
    
    delete p;
    MapIterator it = map.find(p);
    if(it != map.end())    
      map.erase(it);//kann hier crashen
    

  • Mod

    5.3.5/4 [expr.delete]
    The cast-expression in a delete-expression shall be evaluated exactly once. If the delete-expression calls the implementation deallocation function (3.7.3.2), and if the operand of the delete expression is not the null pointer constant, the deallocation function will deallocate the storage referenced by the pointer thus rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate.]

    Unbestimmte Werte darf man nicht anfassen (C99: 3.17.2 -> 6.2.6.1/5).

    Manche Compiler (z.B. Visual C++) gehen soweit, im Debugmodus Zeigervariablen zu verändern, wenn sie als Argumente für delete verwendet wurden.


  • Mod

    Wie kann ein Wert unbestimmt sein, wenn er dadurch unbestimmt wird, dass er einen bestimmten Wert hat? 😕 Die Klausel ist doch selbstwidersprechend.



  • camper schrieb:

    Manche Compiler (z.B. Visual C++) gehen soweit, im Debugmodus Zeigervariablen zu verändern, wenn sie als Argumente für delete verwendet wurden.

    Also würde

    delete p+0;
    

    die Sache wieder retten.



  • SeppJ schrieb:

    Wie kann ein Wert unbestimmt sein, wenn er dadurch unbestimmt wird, dass er einen bestimmten Wert hat? 😕 Die Klausel ist doch selbstwidersprechend.

    unbestimmt heißt aus Sicht des Standards, daß es keine Festlegung gibt, wie dieser Wert auszusehen hat - da kann der Debugger sich also dazwischenschalten und irgendwas reinschreiben, um fehlerhafte Nutzungen zu erkennen.
    (das dürfte so ähnlich funktionieren wie mit nicht-initialisierten Variablen)


  • Mod

    SeppJ schrieb:

    Wie kann ein Wert unbestimmt sein, wenn er dadurch unbestimmt wird, dass er einen bestimmten Wert hat? 😕 Die Klausel ist doch selbstwidersprechend.

    Naja, wenn sie geschrieben hätten:
    "There are no pointer values that refer to deallocated storage."
    wäre das möglicherweise geringfügig exacter gewesen, aber kaum weniger verwirrend.

    volkard schrieb:

    camper schrieb:

    Manche Compiler (z.B. Visual C++) gehen soweit, im Debugmodus Zeigervariablen zu verändern, wenn sie als Argumente für delete verwendet wurden.

    Also würde

    delete p+0;
    

    die Sache wieder retten.

    Es ist dann immer noch undefiniert, p (d.h. den Wert von p) im Anschluss zu verwenden.

    Es gibt auch irgendwo einen FAQ-Artikel in dem gegen Container aus rohen (besitzenden) Zeigern argumentiert wird: Nicht weil sie umständlich sind, sondern weil es zu leicht ist, diese auf eine Weise zu verwenden, die wegen singulärer Zeiger UB zur Folge hat.


  • Mod

    camper schrieb:

    SeppJ schrieb:

    Wie kann ein Wert unbestimmt sein, wenn er dadurch unbestimmt wird, dass er einen bestimmten Wert hat? 😕 Die Klausel ist doch selbstwidersprechend.

    Naja, wenn sie geschrieben hätten:
    "There are no pointer values that refer to deallocated storage."
    wäre das möglicherweise geringfügig exacter gewesen, aber kaum weniger verwirrend.

    Im Prinzip heißt die Klausel aber, dass dies hier in Zeile 5 undefiniert wäre

    int *a = new int, *b = new int;
    if (a - b == 1)
    {
     delete a;
     b = (b + 1) - 1;
    }
    

    Ja, gut, das brauchst du jetzt nicht zu beantworten, wenn's so im Standard steht, ist es eben so. Aber komisch ist das schon wenn es so putzige Effekte hat. Ich frage mich, was man sich dabei gedacht hat.



  • Wurde schon zitiert, aber egal.

    5.3.5/4 schrieb:

    (...) the deallocation function will deallocate the storage referenced by the pointer thus
    rendering the pointer invalid. [Note: the value of a pointer that refers to deallocated storage is indeterminate.]

    So wie das formuliert ist, bedeutet das für mich:

    int* a = new int();
    int* b = a;
    delete a;
    // Wert von a UND b (!) is "indeterminate"
    

    Weiters...

    3.7.3.2/4 schrieb:

    If the argument given to a deallocation function in the standard library is a pointer that is not the null
    pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering
    invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid
    pointer value (including passing it to a deallocation function) is undefined.

    Jetzt wäre natürlich noch interessant, was genau ein "valid pointer" ist, und was "using" hier bedeutet.

    Die gängige Interpretation für "valid pointer" scheint zu sein:
    * Der Null-Pointer (eingeschränkt)
    * Pointer auf "allocated storage" (eingeschränkt)
    * Pointer auf "existierende" Objekte

    Und die gängige Interpretation für "using" scheint zu sein: alles was eine rvalue erzeugt.

    Daraus ergibt sich für mich (in Kombination mit dem ersten Absatz oben):

    int* p = new int();
    int* p2 = p; // OK, zeiger ist ja noch gültig
    
    unsigned char c1 = *reinterpret_cast<unsigned char*>(&p2); // ebenso OK
    
    delete p; // Objekt wird gelöscht, dadurch werden p und p2 "invalid", und ihr Wert wird "indeterminate"
    
    // Im weiteren ist jede Zeile für sich alleine zu sehen...
    
    *p; // Sowieso undefiniert
    sizeof(p); // Vermutlich OK, weiss aber nicht ob C++ 03 das 100% klar macht
    sizeof(*p); // Vermutlich OK, weiss aber nicht ob C++ 03 das 100% klar macht
    
    p = 0; // OK, p wird nur als lvalue verwendet, und 0 ist ein "valid pointer"
    p = p2; // Undefiniert, p2 wird als rvalue verwendet (kein Problem mit p allerdings)
    
    unsigned char c2 = *reinterpret_cast<unsigned char*>(&p2); // OK, aber der Wert von c2 ist nicht definiert (muss also nicht gleich c1 sein!)
    memcpy(&p, &p2, sizeof(int*)); // OK (es wird keine rvalue erzeugt, und &p sowie &p2 zeigen auf "allocated storage")
    memcmp(&p, &p2, sizeof(int*)); // auch OK, muss aber nicht 0 ergeben
    
    size_t s = reinterpret_cast<size_t>(p2); // Undefiniert, p2 wird als rvalue verwendet
    
    bool b = (p == p2); // Undefiniert, p und p2 werden als rvalue verwendet
    
    p2 + 0; // Undefiniert, weil p2 für "p2 + 0" erstmal in eine rvalue konvertiert wird (vor der "Addition")
    
    p; // Pfuh. Keine Ahnung. Wird hier eine rvalue erzeugt? Gilt das als "using"?
    

    @camper, pumuckl & CO:
    Kann man das soweit stehen lassen?

    BTW: Ich hab übrigens heute zum ersten mal Absatz 3.8/4 gelesen. Wer meint dass man den Speicher von non-POD Typen (mit oder ohne nicht-trivialem Destruktor) keinenfalls "einfach so" überschreiben darf (mit memcpy, memset, POD* oder was auch immer), der soll den Absatz mal lesen 🙂



  • SeppJ schrieb:

    Wie kann ein Wert unbestimmt sein, wenn er dadurch unbestimmt wird, dass er einen bestimmten Wert hat? 😕 Die Klausel ist doch selbstwidersprechend.

    Nicht wirklich. Ich habe einen Zeiger der auf einen definierten Speicherbereich zeigt. Das ist der Normalfall. Jetzt hab ich einen Zeiger der auf nichts zeigt. Der bekommt nicht irgendein zufälligen Wert sondern den definierten Wert 0 der genau das sagt: Der Zeiger zeigt nirgends hin. Ein ähnliches Beispiel ist die Funktion find in std::string . Entweder gibt es die Position des gefundenen Substrings im Ausgangsstring zurück oder den definierten Wert std::string::npos . Der macht genau das: Die Position des Substrings ist nicht definiert weil es ihn nicht gibt.

    Zum ursprünglichen Problem: Einfach keine wilden Zeiger verwenden. Ich benutze hier mal bewußt die Unterscheidung zwischen "wilder" Zeiger => Zeigt irgendwo hin was nicht zulässig ist, und "undefiniertem" Zeiger => Hat den Wert 0 und sagt damit aus das der Zeiger nirgendwo hin zeigt. Mit einem undefinierten Zeiger kann das System umgehen, mit einem wilden Zeiger jedoch nicht. Durch das delete entsteht ein wilder Zeiger der nicht weiter benutzt werden darf, da die Compiler so etwas abtesten dürfen und es zu Problemen kommen kann (nicht muss).

    In diesem konkreten Fall heißt das: Das delete darf nicht vor den herauslösen aus der map geschehen => Das sieht nach einem Designfehler oder nach unglücklicher Programmierung aus. Wurde aber auch schon so in der ersten Antwort geschrieben. Wenn an anderer Stelle mit den Pointern aus der map gearbeitet wird darf das garnicht anders.


  • Mod

    SeppJ schrieb:

    int *a = new int, *b = new int;
    if (a - b == 1)
    {
     delete a;
     b = (b + 1) - 1;
    }
    

    Die Bedingung muss etwas anders geschrieben werden ( a == b + 1 ). Allerdings besteht hier noch kein Problem, denn b+1 wird ja erst nach dem delete ausgewertet, und zu diesem Zeitpunkt hat irgendeine Relation zwischen a und b, die vorher bestanden haben mag, keine Bedeutung mehr. Interessant wird es, wenn wir das Ganze etwas umschreiben:

    int* a = new int;
    int* b = new int;
    int* c = b + 1;
    delete a;
    assert( b == c - 1 );
    

    Unser gesunder Verstand sagt uns, dass hier nichts Schlimmes passieren kann.

    int* a = new int;
    int* b = new int;
    int* c = b + 1;
    if ( c == a )
    {
       delete a;
       assert( b == c - 1 );
    }
    else
    {
       delete a;
       assert( b == c - 1 );
    }
    

    ist das nun irgendwie undefiniert, falls der if-Zweig ausgeführt wird? Ich denke nicht, allerdings bin ich nicht gänzlich sicher, dass der Wortlaut des Standards hier für eine definitive Aussage präzise genug ist. Das müsste geprüft werden.

    Für alle, die selbst im Standard nachlesen wollen, weise ich darauf hin, dass im Standard für C++ keine Definition des Begriffs indeterminate zu finden ist, hier greift damit der Verweis in 1.2/1 (in der 2003 Revision) auf den C-Standard in der Version von 1999. Soweit sich relevante Stellen in C99 finden ist aber immer mit zu beachten, dass bestimmte Begriffe (wie etwa l- und rvalue) in C anders definiert sind, so dass die entsprechenden Regeln ggf. nur sinngemäß aber nicht wörtlich zu übertragen sind.


Anmelden zum Antworten