Referenz-Parameter auf nullptr prüfen?



  • Moin 🙂

    Sollte man sich immer darauf verlassen, dass Referenzen als Funktionsparameter gültig sind oder sollte man auch hier die Parameter prüfen?

    void foo(int &bar)
    {
    	bar = 5;
    }
    
    int main()
    {
    	// so ist es ok
    	int var1 = 0;
    	foo(var1);
    	std::cout << var1 << std::endl;
    
    	// wtf? wie unanständig!
    	int *var2 = nullptr;
    	foo(*var2);	 // -> crash
    	std::cout << var2 << std::endl;
    
    	return 0;
    }
    
    Diese Funktion fängt den Fehler bei 'foo(*var2)' ab:
    void foo(int &bar)
    {
    	if (&bar == nullptr)
    		return;
    
    	bar = 5;
    }
    

    viele Grüße,
    SBond



  • Du kannst Referenzen nicht auf nullptr prüfen.

    Schon die Dereferenzierung eines nullptr's ist nicht erlaubt - wenn du prüfen musst/willst, dann musst du das ausserhalb mit dem Zeiger machen.

    // Edit

    PS: Auch wenn dein Bsp. - Code kompiliert, linkt und ausführbar ist, ist es nicht erlaubt, einen nullptr zu dereferenziern.



  • theta schrieb:

    Schon die Dereferenzierung eines nullptr's ist nicht erlaubt

    Ja das ist richtig, daher stürzt das Programm im oberen Beispiel auch ab. Aber ist die Übergabe eines dereferentierten Pointers wirklich ungültig? Das ich hier einen nullptr übergeben habe war natürlich bewusst. Im Normalfall sollte es eine korrekt initialisiert Variable (Pointer) sein. Nun gibt es allerdings auch *Funktionen(), die bei Erfolg eine initialisierte Struktur und beim Fehler einen nullptr zurückgeben. Und genau hier wird es interessant.



  • Der einzige Weg um das von dir beschriebene Problem herzustellen, ist einen nullptr zu dereferenzieren, anders kommt der nullptr gar nicht erst in die Referenz rein...und ja, das ist "ungültig" (undefiniertes Verhalten)...



  • SBond schrieb:

    theta schrieb:

    Schon die Dereferenzierung eines nullptr's ist nicht erlaubt

    Aber ist die Übergabe eines dereferentierten Pointers wirklich ungültig?

    Der Aufruf function(*variable_mit_nullptr) ist UB.

    Wenn man mal in n3376 guckt (ist zwar nicht der offizielle Standard, aber gut genug), dann findet sich auf Seite 192 auch explizit eine Note mit deinem Beispiel:

    A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. — end note ]



  • SBond schrieb:

    ... Aber ist die Übergabe eines dereferentierten Pointers wirklich ungültig?

    Nein, das ist natürlich gültig, mindestens sofern die Referenz auf ein gültiges Objekt zeigt (=es keine dangling Referenz ist).

    SBond schrieb:

    ... Nun gibt es allerdings auch *Funktionen(), die bei Erfolg eine initialisierte Struktur und beim Fehler einen nullptr zurückgeben. Und genau hier wird es interessant.

    Und genau hier ist es auch nicht erlaubt, einen nullptr zu dereferenzieren - es ist UB.

    // Edit

    Wenn du die Funktionen verketten möchtest, kannst du als Argument anstelle einer Referenz einen Zeiger verwenden - den Zeiger kannst du dann in der Funktion überprüfen und es ist erlaubt nullptr zu übergeben. Ob das Sinn macht, hängt vom konkreten Problem ab, dass es zu lösen gilt.



  • Im Normalfall sollte man einen Pointer auf Gültigkeit prüfen, bevor man damit irgendetwas macht.

    Aber es war auch nur aus Interesse heraus gefragt. Zugegeben, genau so etwas ist mir vor knapp 2 Monaten mal passiert:

    meineFunktion(DATENTYP &meinParameter) {...}
    void main ()
    {
    	DATENTYP *myVar = eineLibFunktion(...); // return nullptr bei Fehler
    	meineFunktion(*myVar); // oh no
    }
    

    ...so ist es dann doch besser:

    void main ()
    {
    	DATENTYP *myVar = eineLibFunktion(...); // return nullptr bei Fehler
    	if (!myVar)
    		throw std::string ("irgendwas lief schief");
    
    	meineFunktion(*myVar);
    }
    

    Ich danke euch für die Antwoten 😃



  • SBond schrieb:

    ...so ist es dann doch besser:

    void main ()
    {
    	DATENTYP *myVar = eineLibFunktion(...); // return nullptr bei Fehler
    	if (!myVar)
    		throw std::string ("irgendwas lief schief");
    	
    	meineFunktion(*myVar);
    }
    

    Ansonsten gibt es in C++ sogar vorgefertigte exception typen, die genau dafür gedacht sind:

    throw std::runtime_error( "irgendwas lief schief" );
    

    In der Situation, in der dieser Zustand aber nicht eintreten darf (bzw. bei fehlerfreier Software nicht eintreten kann):

    throw std::logic_error( "Kann nicht sein." );
    

    oder

    assert( yourVar );
    

  • Mod

    wob schrieb:

    A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. — end note ]

    Dieses Zitat ist defekt (und im Übrigen nicht normativ). Ich verfasse gerade ein Paper, dass null lvalues einführt; das Dereferenzieren von Nullzeigern ist nämlich per se kein Problem, nur spezielle Verwendungen des resultierenden lvalues. Bspw. das Binden einer Referenz, wie im Beispiel des OP.



  • Arcoth schrieb:

    das Dereferenzieren von Nullzeigern ist nämlich per se kein Problem, nur spezielle Verwendungen des resultierenden lvalues.

    Ist es nicht eher so dass nur bestimmte Dinge explizit erlaubt sind?


  • Mod

    hustbaer schrieb:

    Arcoth schrieb:

    das Dereferenzieren von Nullzeigern ist nämlich per se kein Problem, nur spezielle Verwendungen des resultierenden lvalues.

    Ist es nicht eher so dass nur bestimmte Dinge explizit erlaubt sind?

    Wobei *(int*)0; nicht explizit erlaubt ist.



  • Arcoth schrieb:

    Dieses Zitat ist defekt (und im Übrigen nicht normativ).

    Dass es nicht normativ ist, ist mir klar, den Standard gibts aber ja nicht frei herunterzuladen. In http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf steht es noch genauso drin, auf Seite 215.

    Was aber meinst du damit, dass es defekt ist? Für mich schien "a null reference cannot exist in a well-defined program" relativ eindeutig zu sein. Aber ich gebe gern zu, dass ich die Passage durch Suchen gefunden hatte und nicht den gesamten Text kenne. Könntest du mich erleuchten, was dem entgegen steht?


  • Mod

    wob schrieb:

    Dass es nicht normativ ist, ist mir klar, den Standard gibts aber ja nicht frei herunterzuladen.

    Das ist ein Implementierungsdetail das nicht interessiert. Wir abstrahieren einfach mal weg und behandeln den Standard als eine Menge von Regeln, kein Dokument.

    Was aber meinst du damit, dass es defekt ist?

    Siehe oben. Das Dereferenzieren eines Nullzeigers ist nicht für sich undefiniert.



  • wob schrieb:

    ... Dass es nicht normativ ist, ist mir klar, den Standard gibts aber ja nicht frei herunterzuladen. In http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf steht es noch genauso drin, auf Seite 215.

    Arcoth hat nicht deswegen "nicht normativ" geschrieben, weil du nicht aus orginalen, aktuell gültigen und veröffentlichten Standard-Dokument zitiert hast, sondern weil es Abschnitte im Standard gibt, die sozusagen nicht zur Norm gehören (nicht zu den Regeln) - sie sind als Kommentar oder Begründung zu verstehen, verpflichten aber nicht.



  • Ehrlich gesagt bin ich wohl ein bisschen schwer von Verständnis:

    1. Das mit dem "normativ" ist durch thetas Kommentar klar. Ok.

    2. Den eigentlichen Punkt habe ich immer noch nicht verstanden. Wenn dieser Satz also nicht normativ ist und nicht gelten muss, wie bekomme ich dann standardkonform in einem gültigen Programm eine auf nullptr zeigende Referenz hin?

    3. Und weiterhin: in welchen Fällen darf ich denn *nullptr machen?



  • Wenn ich Arcoth richtig verstanden habe, geht es darum, dass eine Dereferenzierung eines nullptr's ansich kein Problem darstellt und auch nicht verboten ist - es ist allerdings verboten das Ergebnis der Dereferenzierung zu verwenden (z. B. binden an eine Referenz).

    Ich denke, dass man nullptr's in der Praxis einfach nicht dereferenziert, da man das Ergebnis sowieso nicht verwenden kann.

    // Edit

    Alles in allem dünkt mich die Deref. eines nullptr's ein, zumindest aus meiner Sicht, nicht relevanter Fall im Alltag eines C++ Programmierers.


Log in to reply