Speicherort mit Zeigern verändern



  • Man kann die Zeiger durchaus verändern, wenn man bei swap statt Zeiger Referenzen auf Zeiger übergibgt.

    void swap(int *&x, int *&y);
    


  • Gut, aber wofür braucht man so etwas?



  • Calk schrieb:

    Gut, aber wofür braucht man so etwas?

    Warum sollte eine swap Funktion nicht auch zwei Zeiger tauschen können.
    Orginal nimmt man hier aber eher ein template.
    oder meinst du Referenzen auf Zeiger im Allgemeinen?



  • @Braunstein: Allerdings kann man eine solche Funktion dann nicht mehr als

    swap(&x, &y)
    

    aufrufen - temporäre Ausdrücke können nur an konstante Referenzen gebunden werden, was ja auch logisch ist.

    @Kanikkl: Deine modifizierte Version leckt Speicher, temp wird nie aufgeräumt. Übrigens haben x und y in main keinen "Heapstandort", weil es sich um Stackvariablen handelt. px und py in swap zeigen dementsprechend auch nicht in den Heap, sondern in den Stack.

    Während Speicherverwaltung auf dem Heap eine einigermaßen komplizierte Sache ist, geschieht bei Stackvariablen (in Bezug auf Speicherverwaltung; Kon- und Destruktoren werden natürlich auch ausgeführt) nicht viel mehr, als bei einem Funktionsaufruf den Stackrahmen etwas nach unten und beim Verlassen der Funktion wieder nach oben zu verschieben. x und y bezeichnen dann jeweils ein paar Byte im neu erschlossenen Speicher, und es gibt keinerlei Zeiger, die vertauscht werden könnten.



  • Zum thema Leakn, ja man müsste temp noch deleten, wenn ich das richtig in Erinnerung habe 😛

    Wie gesagt, das war nur ein Beispiel ausm Buch und ich dachte, das müsste doch auch noch anders gehen :P. Ich bin einfach nur stark begeistert von der Materie und zieh mir das bis zum Umfallen rein 😃

    So oder so, ist das nur ein Beispiel für den Einsatz von Zeigern, bzw von Referenzen und wozu man die braucht, habe ich später im Kapitel kennen gelernt. Da Funktionen nur einen Wert ausgeben können, benutzt man Referenzen, um diese an die Funktion zu übergeben und in ihnen ein Argument zu speichern. Damit kann man mehrere Argumente durch eine Funktion an die aufrufende Funktion übergeben.
    -> Das wurde jetzt aber erst später im Kapitel erklärt.

    Jedenfalls danke für eure Geduld 😛



  • Du brauchst jein new int machen.



  • Ah okay, also einfach "int *temp; "?



  • Ja.



  • Eigentlich braucht man überhaupt keine Zeiger, wenn man zwei Werte vertauschen möchte sind zwei Parameter Pflicht, daher finde ich die Verwendung von Referenzen hier deutlich sinnvoller. Vielleicht wurden Referenzen im Buch noch nicht behandelt, hier ist aber trotzdem die Lösung (im Bewusstsein, dass std::swap bereits existiert):

    void swap( int& op1, int &op2 )
    {
       int tmp = op1;
       op1 = op2;
       op2 = tmp;
    }
    


  • Kanikkl schrieb:

    Dieses Programm vertauscht ja die Werte X und Y über die Funktion swap(), indem die HeapStandpunkte von X und Y mit &X und Y& an die swap() weitergegeben. Dort werden aber lediglich die Argumente Vertauscht und nicht die tatsächlichen HeapStandpunkte.

    "HeapStandpunkte", tolles Wort ... 😃

    Unter einem Heapstandpunkt stelle ich mir die Adresse des Prozessorheaps unter Windows vor, so wie:

    #include <windows.h>
    #include <iostream>
    using std::cout;
    
    int main()
    {
        cout<<"Der Heap des Programms liegt bei: "<<GetProcessHeap()<<'\n';
        return 0;
    }
    

    Tatsächlich redet man aber eher von Adressen ... 😉

    Zu deinem Problem: Wenn man Zeiger übergibt, muss man sich erst mal klarmachen, was Zeiger überhaupt sind. Zeiger sind ganz normale Variablen. Theoretisch könnte man durch einen Cast jeden Zeiger in eine unsigned __int32 - oder (wenn der Prozess 64 Bit unterstützt) unsigned __int64 -Variable packen - auch Funktionszeiger (habe ich selbst mal gemacht, weil eine Scriptsprache keine Zeiger kannte, aber, wenn die Adressen bekannt sind, Funktionen im Speicher aufrufen konnte). Dadurch untergräbst du allerdings die Typensicherheit des Compilers ...

    Zeiger sind Variablen. Ihre Werte können allerdings als Adressen interpretiert werden, und diese Adressen beinhalten unter Umständen wichtige Daten. Worauf ein Zeiger zeigt, weiß er selbst nicht, das tut nur der Compiler, der dafür sorgt, dass man nicht unachtsam den Zeiger ändert. Daher kann ein Zeiger die Adresse eines Objekts beinhalten, welches auf dem Stack liegt - aber auch auf Elemente auf dem Heap.

    Parameterübergabe bedeutet, dass das angegebene Objekt kopiert (!) wird und an die aufgerufene Funktion übergeben wird - das passiert bei allen Objekten, die man übergibt, auch bei Zeigern (außer bei Referenzen, glaube ich, aber die verwende ich nie, daher kann ich wenig über DIE sagen).

    Folgendes Beispiel:

    #include<iostream>
    using std::cout;
    
    void CopyPointer(int*Pointer)
    {
        cout<<"Der Wert des Objektes: "<<*Pointer<<'\n';
        cout<<"Der Wert des Zeigers (Adresse des gesicherten Objekts): "<<Pointer<<'\n';
        cout<<"Die Adresse des Zeigers: "<<&Pointer<<"\n\n";
    }
    
    int main()
    {
        int First=5,*Second=&First,*Third=new int(10);
    
        cout<<"Der Wert des Objektes: "<<First<<'\n';
        cout<<"Der Wert des Zeigers (Adresse des gesicherten Objekts): "<<&First<<"\n\n";
    
        cout<<"Der Wert des Objektes: "<<*Second<<'\n';
        cout<<"Der Wert des Zeigers (Adresse des gesicherten Objekts): "<<Second<<'\n';
        cout<<"Die Adresse des Zeigers: "<<&Second<<"\n\n";
    
        cout<<"Der Wert des Objektes: "<<*Third<<'\n';
        cout<<"Der Wert des Zeigers (Adresse des gesicherten Objekts): "<<Third<<'\n';
        cout<<"Die Adresse des Zeigers: "<<&Third<<"\n\n";
    
        CopyPointer(&First);//Adresse der Variablen.
        CopyPointer(Second);//Wert des Zeigers, entspricht der Adresse von First.
        CopyPointer(Third);//Adresse des Objektes auf dem Heap.
    
        delete Third;//Zeigt auf Speicher auf dem Heap ...
        return 0;
    }
    

    In beiden Funktionen bleiben die Werte der dereferenzierten Objekte und die Werte der Zeiger gleich, aber die Adressen der Zeiger selbst sind in main und CopyPointer unterschiedliche. Die Objekte, auf die die Zeiger zeigen, bleiben die selben und werden NICHT kopiert, aber die Zeiger selbst werden kopiert. Daher kann man in CopyPointer Pointer alles zuweisen, was man will, auf den Zeiger in main hat das keine Auswirkungen (es sei denn, man manipuliert das Objekt, auf das die Zeiger verweisen, aber selbst dann bleibt der Zeiger auf dieses Objekt fixiert).

    Wenn man also die Werte der Zeiger ändern will, sodass auch main davon weiß, braucht man Referenzen auf Zeiger (habe ich zumindest in Erinnerung):

    #include<iostream>
    using std::cout;
    
    void swap(int*&X,int*&Y)//Oder '&*'? Ich versuche normalerweise, Referenzen so weit wie möglich zu vermeiden ...
    {
        int*Temp;
    
        std::cout<<"swap - vor Vertauschung: *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"swap - vor Vertauschung: X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"swap - vor Vertauschung: &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
    
        Temp=X;
        X=Y;
        Y=Temp;
    
        std::cout<<"swap - nach Vertauschung: *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"swap - nach Vertauschung: X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"swap - nach Vertauschung: &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
    }
    
    int main()
    {
        //X und Y brauchen wir auf dem Heap, da wir richtige Zeiger übergeben
        //müssen. Funktionen, die Referenzen verwenden, brauchen ein Objekt, auf
        //das sie sich mit einer Adresse beziehen können, und ein temporär
        //übergebenes Objekt reicht da nicht, wir brauchen Speicher ...
        int*X=new int(5),*Y=new int(10);
    
        std::cout<<"main - vor Vertauschung. *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"main - vor Vertauschung. X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"main - vor Vertauschung. &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
    
        //Jetzt übergeben wir eine Referenz auf die Werte der Zeiger ...
        //Das ist jetzt schon ziemlich komplexes C++ ...
        swap(X,Y);//Übergibt die Adressen als Referenzen. Alles, was wir den Zeigern in swap machen, hat Auswirkungen auf die Zeiger in main ...
    
        std::cout<<"main - nach Vertauschung. *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"main - nach Vertauschung. X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"main - nach Vertauschung. &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
        return 0;
    }
    

    Ich habe es nicht ausprobiert oder kompiliert, aber es sollte so funktionieren. Vielleicht ein oder zwei Rechtschreibfehler oder das "&" in "&" ändern, aber das Grundgerüst steht.

    Hierbei übergibt man die Adressen der Objekte als Referenzen - wenn man diese ändert, ändert sich auch das Original. Aber moment mal, während ich das schreibe, fällt mir ein, dass das Ganze auch mit Zeigern auf Zeiger funktioniert ...

    #include<iostream>
    using std::cout;
    
    void swap(int**X,int**Y)
    {
        int*Temp;
    
        //ACHTUNG: X ist ein Zeiger auf unsere Zeiger in main, X in swap gleicht
        //also der Adresse von X in main ... ziemlich komplex eben.
    
        std::cout<<"swap - vor Vertauschung: **X: "<<**X<<", **Y: "<<**Y<<'\n';
        std::cout<<"swap - vor Vertauschung: *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"swap - vor Vertauschung: &(*X): "<<&(*X)<<", &(*Y): "<<&(*Y)<<"\n\n";
    
        *Temp=*X;
        *X=*Y;
        *Y=*Temp;
    
        std::cout<<"swap - nach Vertauschung: **X: "<<**X<<", **Y: "<<**Y<<'\n';
        std::cout<<"swap - nach Vertauschung: *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"swap - nach Vertauschung: &(*X): "<<&(*X)<<", &(*Y): "<<&(*Y)<<"\n\n";
    }
    
    int main()
    {
        int*X=new int(5),*Y=new int(10);
    
        std::cout<<"main - vor Vertauschung. *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"main - vor Vertauschung. X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"main - vor Vertauschung. &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
    
        //Jetzt übergeben wir die Adressen der Zeiger, also Zeiger auf Zeiger ...
        //Das ist noch komplexer, zumindest von der Syntax her ...
        swap(&X,&Y);
    
        std::cout<<"main - nach Vertauschung. *X: "<<*X<<", *Y: "<<*Y<<'\n';
        std::cout<<"main - nach Vertauschung. X: "<<X<<", Y: "<<Y<<'\n';
        std::cout<<"main - nach Vertauschung. &X: "<<&X<<", &Y: "<<&Y<<"\n\n";
        return 0;
    }
    

    Hierbei werden die Adressen der Zeiger (nicht die Werte) übergeben. Dadurch werden die Adressen der Zeiger nicht kopiert, und Manipulationen betreffen auch das Original. Allerdings wirst du es nicht schaffen, einen Zeiger auf X in main zu erstellen und ihn dann in swap zu manipulieren, dafür brauchst du Zeiger auf Zeiger auf Zeiger ... 🕶

    Mir egal, welche Version du verwendest, beide sollten funktionieren. Ich hoffe nur, ich konnte helfen ...


Anmelden zum Antworten