Verständniss frage zu Zeigern



  • Tachyon schrieb:

    Für den Compilerbauer ist es hingegen hilfreich zu wissen, dass eine Referenz einen Kontrakt darstellt, der an viel engere Rahmenbedingungen geknüpft ist, als ein Zeiger. Deshalb ermöglichen Referenzen in vielen Fällen Optimierungen, die so mit Zeigern nicht möglich wären.

    Sorry, das ist nicht korrekt. Diese Optimierungen kann der Compiler nur dann machen, wenn er in dem jeweiligen Kontext sowohl die Referenz als auch das referenzierte Objekt kennt. Wenn er nur die Referenz kennt, sind die Optimierungen nicht mehr möglich. Bei Zeigern ist die Optimierung genauso möglich, wenn der Compiler sowohl den Zeiger als auch das Objekt kennt, auf das der Zeiger verweist.

    Zeige mir ein Beispiel, wo es nicht so ist.

    mfg Martin



  • Tachyon schrieb:

    Die Betrachtungsweise "Referenz == verkappter Zeiger" führt übrigens auch zu so tollen Aussagen wie: "Ich benutze lieber Zeiger, weil man da gleich sieht, dass das eine Referenz übergeben wird,

    Das ist kein Schwachfug.

    Tachyon schrieb:

    und es eh keine Unterschiede gibt." Was für ein Schwachfug.

    Korrekt, das ist Unfug.

    Wo sind denn nun die Unterschiede?

    1. Referenzen müssen initialisiert werden und dabei auf ein gültiges Objekt verweisen. Zeiger dürfen eine beliebige Adresse enthalten und dürfen sogar uninitialisiert benutzt werden.
    2. Referenzen kennen keine Pointerarithmetik. Referenzen können nur verändert werden, indem man sie auf ein anderes Objekt verweisen lässt.
    3. Referenzen werden automatisch dereferenziert. Man kann sie also nicht dazu benutzen, um die Adresse des referenzierten Objektes zu erhalten.

    Was ist nun der Vorteil?
    1. Da Referenzen initialisiert werden müssen, kann es nicht so leicht passieren, das ungültige Referenzen erzeugt werden. Unmöglich ist es aber nicht, wie ich schon gezeigt habe.
    2. Da Referenzen automatisch dereferenziert werden, ist der Code leichter zu verstehen:

    void func1( const INT_ARRAY &ref )
    {
       int value = ref[10];
    }
    

    vs.

    void func2( const INT_ARRAY *pointer )
    {
       int value = (*pointer)[10];
    ]
    

    Wenn ich eine Funktion habe, die ein größeres Objekt erhält, welches nicht verändert werden soll, benutze ich const Referenzen. Wenn das Objekt verändert werden soll, benutze ich Zeiger. Warum?

    Nehmen wir an, ich habe ein Problem, daß ein Objekt einen falschen Wert erhält und ich weiß nicht wo. Im Debugger kann ich nun alle Funktionen überspringen, die nur eine Referenz erhalten, weil bei mir die Regel gilt keine Referenzen auf veränderbaren Objekten. Das spart viel Zeit bei der Fehleranalyse.

    mfg Martin



  • Du hast einen Unterschied vergessen:

    Zeiger sind der häufigste Grund für Programmabstürze! Nämlich dann, wenn sie auf nix zeigen und dereferenziert werden. Oder heißt es dann depointerisiert (ist ja keine Referenz)? Interessanterweise benutzt man auf bei Zeigern den Ausdruck "dereferenzieren", obwohl sie keine Referenzen sind. Faszinierend.

    Mich würd noch interessieren, ob der Thread-Ersteller dieser Diskussion weiter folgt...



  • mgaeckler schrieb:

    Referenzen können nur verändert werden, indem man sie auf ein anderes Objekt verweisen lässt.

    Wie geht denn sowas?



  • mgaeckler schrieb:

    [...]
    2. Referenzen kennen keine Pointerarithmetik. Referenzen können nur verändert werden, indem man sie auf ein anderes Objekt verweisen lässt.
    3. Referenzen werden automatisch dereferenziert. Man kann sie also nicht dazu benutzen, um die Adresse des referenzierten Objektes zu erhalten.
    [...]

    100. Du hast Referenzen nicht verstanden.

    Zu 2: Man kann Referenzen nicht auf ein anderes Objekt verweisen lassen nachdem sie initialisiert wurden.
    Zu 3: Klar kann man das. Man kommt nur nicht an die Adresse des "Referenzobjekts" heran, weil die Referenz kein Objekt ist.



  • mgaeckler schrieb:

    Wo sind denn nun die Unterschiede?

    1. Referenzen müssen initialisiert werden und dabei auf ein gültiges Objekt verweisen. Zeiger dürfen eine beliebige Adresse enthalten und dürfen sogar uninitialisiert benutzt werden.
    2. Referenzen kennen keine Pointerarithmetik. Referenzen können nur verändert werden, indem man sie auf ein anderes Objekt verweisen lässt.
    3. Referenzen werden automatisch dereferenziert. Man kann sie also nicht dazu benutzen, um die Adresse des referenzierten Objektes zu erhalten.

    2 und 3 sind klar falsch. Erstens können Referenzen nach der Initialisierung nicht mehr verändert werden, zweitens ist es sehr wohl möglich, die Adresse des referenzierten Objektes zu erhalten:

    T t;
    T& r = t;
    T* p = &r; // == &t
    

    2. Da Referenzen automatisch dereferenziert werden, ist der Code leichter zu verstehen:

    void func1( const INT_ARRAY &ref )
    {
       int value = ref[10];
    }
    

    vs.

    void func2( const INT_ARRAY *pointer )
    {
       int value = (*pointer)[10];
    ]
    

    Das Beispiel hätte man besser ohne Pointer oder Referenz geschrieben, da Arrays ohnehin als Pointer übergeben werden. Im Allgemeinen ist deine Aussage nämlich nicht zu halten.



  • mgaeckler schrieb:

    Sorry, das ist nicht korrekt. Diese Optimierungen kann der Compiler nur dann machen, wenn er in dem jeweiligen Kontext sowohl die [...]

    Tja, da Du nicht wusstest, dass gebundene Refenezen wärend ihrer Lebenszeit konstant bleiben... 🙄

    Pointer sind variabel. Es ist viel schwieriger zu sie optimieren (vor allem, wenn der Code der sie benutzt nicht nur ein exemplarischer Einzeiler ist) da man damit rechnen muss, dass sie neu belegt werden.

    Referenzen sind, wie gesagt für ihre Lebenszeit immer an das gleiche Objekt gebunden. Das ermöglicht Optimierungen auch ohne den Kontext zu kennen...



  • mgaeckler schrieb:

    Nehmen wir an, ich habe ein Problem, daß ein Objekt einen falschen Wert erhält und ich weiß nicht wo. Im Debugger kann ich nun alle Funktionen überspringen, die nur eine Referenz erhalten, weil bei mir die Regel gilt keine Referenzen auf veränderbaren Objekten. Das spart viel Zeit bei der Fehleranalyse.

    Dafür gibt es Watchpoints, das ist also nicht unbedingt ein Argument.



  • Bashar schrieb:

    Das Beispiel hätte man besser ohne Pointer oder Referenz geschrieben, da Arrays ohnehin als Pointer übergeben werden. Im Allgemeinen ist deine Aussage nämlich nicht zu halten.

    typedef int ARRAY[10];
    
    void f1(ARRAY a)
    {
        std::cout << sizeof(a) << '\n';
    }
    
    void f2(ARRAY & a)
    {
        std::cout << sizeof(a) << '\n';
    }
    
    int main()
    {
        ARRAY a = {0};
    
        f1(a);
        f2(a);
    }
    

    Merke: Array != Pointer => Array & != Pointer &...



  • Tachyon schrieb:

    Merke: Array != Pointer => Array & != Pointer &...

    Ich hab geschrieben "das Beispiel", nicht "immer".



  • Bashar schrieb:

    Tachyon schrieb:

    Merke: Array != Pointer => Array & != Pointer &...

    Ich hab geschrieben "das Beispiel", nicht "immer".

    Okay, hast ja recht. Sorry. 🤡

    Mir fällt übrigens ein Beispiel aus einem Inteldokument ein in dem es um Schleifenoptimierung und Abbruchbedingungen ging:

    while(*p < != condition)
    {
       //zeugs
    }
    

    vs.

    while(r < != condition) //r soll ref sein
    {
       //zeugs
    }
    

    Bei der ersten Version hat der Compiler (laut Intel) keine Möglichkeit den Lookup über die Adresse wegzuoptimieren, weil er nicht vorhersehen kann, ob p nicht innerhalb der Schleife geändert wird.
    Das Dokument hieß coding.pdf (okay, das ist nicht sehr hilfreich). Leider habe ich es nur noch in ausgedruckter From.



  • Tachyon schrieb:

    Tja, da Du nicht wusstest, dass gebundene Refenezen wärend ihrer Lebenszeit konstant bleiben... 🙄

    Gut, da hab ich Mist geschrieben.

    Tachyon schrieb:

    Pointer sind variabel. Es ist viel schwieriger zu sie optimieren (vor allem, wenn der Code der sie benutzt nicht nur ein exemplarischer Einzeiler ist) da man damit rechnen muss, dass sie neu belegt werden.

    Referenzen sind, wie gesagt für ihre Lebenszeit immer an das gleiche Objekt gebunden. Das ermöglicht Optimierungen auch ohne den Kontext zu kennen...

    Trotzdem ist es für einen Compiler nicht sehr schwierig, festzustellen, ob ein Zeiger verändert wird oder nicht. Aber Du hast schon recht, bei einer Referenz ist es leichter.

    Ich zeig jetzt mal ein Beispiel, wo eine Referenz deutliche Vorteile gegenüber einem Zeiger hat:

    class STRING
    {
       ...
       STRING( const char *value );
       ...
    };
    
    void func1( const STRING &ref );
    void func2( const STRING *pointer );
    
    int main
    {
       func1( "HTML" );
    
       STRING html = "HTML";
       func2( &html );
    }
    

    Für den Aufruf von func1 muß ich kein temporäres Objekt explizit erzeugen, das macht der Compiler für mich. Für func2 geht das nicht, da muß explizit das STRING-objekt erzeugt werden.

    mfg Martin



  • Bashar schrieb:

    2 und 3 sind klar falsch. Erstens können Referenzen nach der Initialisierung nicht mehr verändert werden, zweitens ist es sehr wohl möglich, die Adresse des referenzierten Objektes zu erhalten:

    T t;
    T& r = t;
    T* p = &r; // == &t
    

    Ich geb's zu, da hab ich Mist geschrieben. 😞

    Bashar schrieb:

    mgaeckler schrieb:

    2. Da Referenzen automatisch dereferenziert werden, ist der Code leichter zu verstehen:

    void func1( const INT_ARRAY &ref )
    {
       int value = ref[10];
    }
    

    vs.

    void func2( const INT_ARRAY *pointer )
    {
       int value = (*pointer)[10];
    ]
    

    Das Beispiel hätte man besser ohne Pointer oder Referenz geschrieben, da Arrays ohnehin als Pointer übergeben werden. Im Allgemeinen ist deine Aussage nämlich nicht zu halten.

    Nur hat man dann keine Möglichkeit einen Pufferüberlauf zu vermeiden. INT_ARRAY ist in diesem Fall eine Containerklasse, die genau das vermeiden soll.

    mfg Martin



  • LordJaxom schrieb:

    Dafür gibt es Watchpoints, das ist also nicht unbedingt ein Argument.

    Watchpoints kosten im Debugger Laufzeit, wenn man nicht gerade eine Hardware hat, die das unterstützt. Wenn man erst einmal den Fehler durch Codeanalyse finden will, hat man nicht einmal das.

    mfg Martin


Anmelden zum Antworten