Nach delete Zeiger benutzbar?



  • 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.


  • Mod

    camper schrieb:

    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.

    Die Antwort auf diese Frage ist aber die gleiche, ob es ok ist, einen Zeiger mit diesem Wert in einer map zu haben.


  • Mod

    SeppJ schrieb:

    camper schrieb:

    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.

    Die Antwort auf diese Frage ist aber die gleiche, ob es ok ist, einen Zeiger mit diesem Wert in einer map zu haben.

    Nicht unbedingt. Für den Fall, dass die Standardallokationsfunktion niemals direkt benachbarte Speicherbereiche anfordert, wird der if-Zeig niemals ausgeführt werden, die Frage stellt sich dann schon nicht mehr. Eine selbstdefinierte Allokationfunktion wiederum (die evtl. solche aufeinanderfolgenden Allokationen ermöglich) ist Teil des Programmes, und ein per delete freigegebener Speicherbereich wird vermutlich erst einmal in einen internen Pool wandeln, d.h. aus Sicht der Sprache bleibt der Speicher reserviert, mithin bleiben Zeiger gültig.
    Nicht umsonst handelt es sich im oben zitierten Code nur um eine Anmerkung, keine eigenständige Regel. Die Grundlage dafür findet sich im C-Standard:

    C99 6.2.4/2 schrieb:

    The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,25) and retains its last-stored value throughout its lifetime.26) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime.



  • camper schrieb:

    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 ).

    Ich kann da jetzt keinen Unterschied erkennen. Warum ist es hier nötig die Bedingung so zu schreiben?



  • hourmin schrieb:

    camper schrieb:

    Die Bedingung muss etwas anders geschrieben werden ( a == b + 1 ).

    Ich kann da jetzt keinen Unterschied erkennen. Warum ist es hier nötig die Bedingung so zu schreiben?

    a-b ist nur erlaubt, wenn a und b in dasselbe Objekt hinein zeigen. Das ist hier nicht der Fall. b+1 ist ein Zeiger hinter das Ende von b, das ist erlaubt. Und zwei beliebige Zeiger, also auch a und b+1, dürfen auf Gleichheit geprüft werden.

    camper, richtig?


  • Mod

    Bashar schrieb:

    hourmin schrieb:

    camper schrieb:

    Die Bedingung muss etwas anders geschrieben werden ( a == b + 1 ).

    Ich kann da jetzt keinen Unterschied erkennen. Warum ist es hier nötig die Bedingung so zu schreiben?

    a-b ist nur erlaubt, wenn a und b in dasselbe Objekt hinein zeigen. Das ist hier nicht der Fall. b+1 ist ein Zeiger hinter das Ende von b, das ist erlaubt. Und zwei beliebige Zeiger, also auch a und b+1, dürfen auf Gleichheit geprüft werden.

    camper, richtig?

    Das ist die Idee. Ist Bedingung (in der korrigierten Form) wahr, kann man das Ganze natürlich auch so sehen, dass a und b zusammen einen zusammenhängenden Speicherbereich bilden, mithin wäre a-b in diesem Fall auch definiert.

    Ist die Bedingung hingegen nicht erfüllt, hätte die Subtraktion unmittelbar UB zur Folge. Mit diesem Wissen könnte ein Compiler auf die Idee kommen, die Bedingung (in ihrer ursprünglichen Form) einfach zu eliminieren und den if-Zweig immer auszuführen.

    Edit: Die andere Möglichkeit, die nicht zur UB zur Folge hätte, ware a+1 == b.


Anmelden zum Antworten