Raw Pointer und Null



  • Hallo,
    ich habe eine Frage zum Umgang mit Nullpointern, primär im Zusammenhang mit alten Raw Pointern.

    1. Wozu benötigt man Nullpointer?
      Zur Initialisierung? Sollte jedem Pointer zunächst eine Null zugewiesen werden?
      Falls ja, mir ist nicht ganz klar, welchen Vorteil das bietet.
    double *P1 = nullptr;
    double x = 20;
    P1 = &x;
    
    1. Sollte in diesem Zusammenhang bei jeder Benutzung geprüft werden
    if (P1) {
    ...
    }
    
    1. Bringt das etwas zur Vermeidung bzw. Erkennung von Dangling Pointern?

    2. Gilt das gleiche für Smartpointer?

    std::shared_ptr<int> p;
    if (!p) {
    ...
    }
    

    Danke für die Hilfe



  • @Mond sagte in Raw Pointer und Null:

    Sollte jedem Pointer zunächst eine Null zugewiesen werden?

    Nein

    double x = 20;
    double* P1 = &x;
    


  • Parameter, die einen Typ erwarten, dieser aber auch NULL sein kann, wenn er nicht gebraucht wird oder nicht da ist.



  • @Mond sagte in Raw Pointer und Null:

    Hallo,
    ich habe eine Frage zum Umgang mit Nullpointern, primär im Zusammenhang mit alten Raw Pointern.

    1. Wozu benötigt man Nullpointer?
      Zur Initialisierung? Sollte jedem Pointer zunächst eine Null zugewiesen werden?

    Nein! Wenn du sinnvoll mit einem anderen Wert initialisieren kannst, dann mach das. Nur wenn das nicht geht, nimm nullptr.

    Benötigt wird er zum Beispiel dann, wenn man optionale Daten hat. Zum Beispiel bei einer verketteten Liste, wo jedes Element seinen Nachfolger kennt. Aber irgendein Element ist das letzte und hat keinen Nachfolger. Dann also nullptr. Genauso musst du irgendwo einen Zeiger auf das erste Element haben. Aber was ist, wenn die Liste leer ist und es kein erstes Element gibt?

    1. Sollte in diesem Zusammenhang bei jeder Benutzung geprüft werden

    Einmalig reicht, solange du den Pointer nicht änderst.

    1. Bringt das etwas zur Vermeidung bzw. Erkennung von Dangling Pointern?

    Also ein nullptr ist niemals ein dangling pointer. Aber DP haben ja andere Ursachen und ich sehe gerade nicht den Zusammenhang - außer dass ein Wieder-auf-nullptr-setzen den DP in einen nullptr umwandelt. Aber eigentlich helfen Smartpointer besser - und insbesondere klare Verhältnisse bei der Ownership.

    1. Gilt das gleiche für Smartpointer?

    prinzipiell ja.



  • @Mond
    Ich glaube dir ist nicht ganz klar, dass Zeiger unterschiedliche Semantiken haben können. Zum einen gibt es besitzende Zeiger, die für das, auf das sie zeigen, verantwortlich sind. Zum anderen gibt es Zeiger, die halt auf irgendwas zeigen und sich nicht drum kümmern müssen, wer die Resource/Objekt besitzt, auf das sie zeigen. die smart pointer sind nur für den ersten Fall gedacht, da sie die Resource, die sie halten, automatisch wieder freigeben

    Beispiel besitzende Zeiger:

    // ohne smart pointer, absolutes no-go
    Object* create_object()
    {
       Object retval = new Object();
       ...
       return retval;   
    }
    
    int main()
    {
       Object* obj = create_object();
       obj->do_something();
       delete obj;
    }
    

    vs.

    smart_ptr<Object> create_object()
    {
       shared_ptr<Object> retval = make_shared<Object>();
       ...
       return retval;
    }
    
    int main()
    {
       shared_ptr<Object> obj = create_object();
       obj->do_something();
    }
    

    Beispiel "normale" Zeiger

    // obj ist kein besitzender Zeiger, sondern übernimmt die Rolle eines optionalen Parameters. Wenn der Parameter 
    // gültig ist wird etwas mit ihm gemacht, ansonsten halt nicht.
    void func( Object* obj )
    {
       // obj ist hier optional
       if( obj )
       {
          obj->do_something();
       }
       else
       {
          do_something_else();
       }
    }
    
    int main()
    {
       Object obj;
       func( nullptr );
       func( &obj );
    }
    


  • @wob sagte in Raw Pointer und Null:

    1. Bringt das etwas zur Vermeidung bzw. Erkennung von Dangling Pointern?

    Also ein nullptr ist niemals ein dangling pointer. Aber DP haben ja andere Ursachen und ich sehe gerade nicht den Zusammenhang - außer dass ein Wieder-auf-nullptr-setzen den DP in einen nullptr umwandelt. Aber eigentlich helfen Smartpointer besser - und insbesondere klare Verhältnisse bei der Ownership.

    Damit war gemeint, folgenden Fall zu prüfen.

    if (P1) {
    ...
    }
    

    Aber falls es wirklich ein Dangling Pointer sein sollte (P1=Dangling Pointer), gibt es wahrscheinlich bei dieser Abfrage dennoch einen Crash.



  • @Mond
    Nein, bei der Abfrage gibt es keinen Crash, es wird ja lediglich geprüft, ob die Zeigervariable kein nullptr ist. In dem Moment, wo du etwas benutzt, auf das der Zeiger zeigt, erzeugst du undefiniertes Verhalten und möglicherweise scheppert´s.



  • D.h. mit

    if (P1) {
    ...
    }
    

    kann man nur prüfen, ob P1 ein Nullptr ist, nicht ob der Pointer ins Nirvana zeigt?



  • @Mond sagte in Raw Pointer und Null:

    D.h. mit

    if (P1) {
    ...
    }
    

    kann man nur prüfen, ob P1 ein Nullptr ist, nicht ob der Pointer ins Nirvana zeigt?

    Ja, richtig!



  • @Mond sagte in Raw Pointer und Null:

    ob der Pointer ins Nirvana zeigt?

    ich bin mir ziemlich sicher daß die Verwendung eines invalid pointer values UB ist.

    // edit: Ok, es ist implementation defined behaviour. basic.stc/4

    Any other use of an invalid pointer value has implementation-defined behavior. 35

    Ich bin trotzdem der Meinung daß man ein wirklich ernstes Problem hat wenn man an einer Stelle im Programm keine Ahnung mehr hat ob ein Pointer gültig ist oder nicht.

    35) Implementations can define that copying an invalid pointer value causes a system-generated runtime fault.



  • @Swordfish

    ich bin mir ziemlich sicher daß die Verwendung eines invalid pointer values UB ist.

    // edit: Ok, es ist implementation defined behaviour. basic.stc/4

    Any other use of an invalid pointer value has implementation-defined behavior.35

    Interessant! Ich meine das ist neu und war früher mal UB.
    Wobei IB und UB jetzt eh keinen grossen Unterschied macht 🙂


  • Mod

    @hustbaer sagte in Raw Pointer und Null:

    Interessant! Ich meine das ist neu und war früher mal UB.
    Wobei IB und UB jetzt eh keinen grossen Unterschied macht 🙂

    Da kann die Implementation halt sagen, dass es kein UB ist. Da die Gründe für das UB an der Stelle ja sicherlich in irgendwelchen Antiquitäten vonwegen Near- und Farpointern lagen, ist das nicht ganz unvernünftig, wenn eine moderne Implementierung sagen kann, dass ihr der konkrete numerische Wert völlig egal ist. Wobei Swordfish schon Recht hat: Wenn man einen Pointer hat, dessen Wert man nicht kennt (außer es geht um den uninitialisierten Anfangswert), dann hat man die Kontrolle über sein Programm verloren. Da ist UB dann auch egal, wenn man das gewollte Verhalten nicht einmal kennt.



  • @hustbaer Ja, ich habe mir auch eingebildet daß es UB ist. War mir aber nicht sicher und habe nachgesehen. Eine Literaturarbeit darüber schreibe ich aber nicht.


  • Mod

    @Swordfish sagte in Raw Pointer und Null:

    @hustbaer Ja, ich habe mir auch eingebildet daß es UB ist. War mir aber nicht sicher und habe nachgesehen. Eine Literaturarbeit darüber schreibe ich aber nicht.

    Ich bin auch sehr sicher, dass das in früheren Standards mal UB gewesen sein muss. Da haben wir hier im Forum kontroverse Diskussionen drüber gehabt, an deren ungefähren Inhalt ich mich noch erinnere. Irgendwas darüber, ob der Wert direkt eine Stelle hinter dem Ende eines Arrays noch definiert ist. Ich weiß aber nicht mehr, was das Ergebnis davon war.



  • @SeppJ Es scheint wohl Architekturen zu geben die trappen wenn man ein invalid pointer value in ein Register lädt.

    C99 6.3.2.3 Pointers/20:

    Regardless how an invalid pointer is created, any use of it yields undefined behavior. Even assignment, comparison with a null pointer constant, or comparison with itself, might on some systems result in an exception.

    Ach so, das war jetzt Rationale for International Standard — Programming Languages — C, Revision 5.10 April-2003



  • @SeppJ sagte in Raw Pointer und Null:

    Irgendwas darüber, ob der Wert direkt eine Stelle hinter dem Ende eines Arrays noch definiert ist. Ich weiß aber nicht mehr, was das Ergebnis davon war.

    Eins über das Ende eines Arrays ist kein invalid pointer value.

    basic.compound/3

    [...]
    Every value of pointer type is one of the following:

    (3.1) a pointer to an object or function (the pointer is said to point to the object or function), or

    (3.2) a pointer past the end of an object ([expr.add]), or

    (3.3) the null pointer value for that type, or

    (3.4) an invalid pointer value.

    @SeppJ sagte in Raw Pointer und Null:

    Standards

    Standarten. ftfy 😉



  • @SeppJ sagte in Raw Pointer und Null:

    Da kann die Implementation halt sagen, dass es kein UB ist.

    Die Implementierung kann genaugenommen immer sagen dass etwas definiertes Verhalten hat. Auch wenn im Standard UB steht. IB wird denke ich nur verwendet wenn es irgendwelche Einschränkungen gibt was erlaubt ist und was nicht. Und in diesem Fall verstehe ich den Text mal so dass es erlaubt ist zu crashen, aber nicht Shakespeare's gesammelte Werke an den Drucker zu schicken.

    Da die Gründe für das UB an der Stelle ja sicherlich in irgendwelchen Antiquitäten vonwegen Near- und Farpointern lagen, ist das nicht ganz unvernünftig, wenn eine moderne Implementierung sagen kann, dass ihr der konkrete numerische Wert völlig egal ist.

    Ja. Und die Gründe sind mir klar. Segmentregister und ähnliche Dinge.

    Wobei Swordfish schon Recht hat: Wenn man einen Pointer hat, dessen Wert man nicht kennt (außer es geht um den uninitialisierten Anfangswert), dann hat man die Kontrolle über sein Programm verloren. Da ist UB dann auch egal, wenn man das gewollte Verhalten nicht einmal kennt.

    Jain. Wenn man nirgends im Programm weiss ob der Wert noch gilt oder nicht, dann hat man ein Problem. Bzw. wenn man es an einer Stelle wo man es auch ohne diese spezielle UB/IB Regel wissen müsste nicht feststellen kann.

    Gibt aber sicher Fälle wo es einen gar nicht interessiert ob der Wert gültig ist oder nicht - bzw. man es gar nicht prüfen will. Wenn man z.B. nur ein paar Werte (z.B. Zeiger) von A nach B kopieren möchte und es wurscht ist ob der Zeiger nun gültig ist oder nicht.

    Also Beispielsweise wenn ich optional implementieren würde, dann könnte mir einfallen für "trivial types" eine Spezialisierung zu machen bei der beide Variablen ("have_value" und "value") immer initialisiert werden, und die "value" nie zerstört sondern nur "have_value = false" gesetz wird. Also genau so wie man es z.B. ohne Templates selbst für einen int schreiben würde. Dann dürfte ich beim Kopieren/Zuweisen immer beide Werte kopieren. Spart ein paar unnötige "if"s ein. Darf ich aber nicht wenn eins davon ein Zeiger ist, und mir dabei alles um die Ohren fliegt wenn der nicht mehr gültig ist.


Log in to reply