Zugriff auf zerstörtes Objekt möglich



  • noch schnell etwas für die logik. für den compiler sieht folgendes aus wie ein zeiger auf einen zeiger:

    int *p_i = NULL;
    DoAnything(&p_i); //<<-- also &p_i sieht für den compiler aus wie ein zeiger auf einen zeiger
    

    da p_i aber kein zeiger auf einen zeiger ist, sondern mehr **p_i, stelle ich mir das so vor: der compiler weiss das p_i ein zeiger ist. man über gibt nun aber anstatt den zeiger selber oder nur den wert, die adresse des zeigers, also eins höher als der wert (die adresse auf die gezeigt wird) ansich. somit weiss der compiler dann es muss sich logischerweise um einen zeiger auf einen zeiger handeln, obwohl es theoretisch nicht ganz so ist? prinzipiell würde es ja stimmen weil genau dieser adresswert ein **p_i ja erwarten würde. aber da dann *p_i keine weitere adresse mehr hat, da dies bereits die eigene zeiger adresse ist, stimmt es eben nicht ganz. sonst wäre ja ein **p_i rein theoretisch gleich ein ***p_i.

    ich hoffe es ist verständlich.



  • Das mit dem Rückgabewert stimmt. Ich habe aber nur eine Andwenungsmöglichkeit gezeigt, welche auch wirklich benutzt wird. Da der Rückgabewert oftmals einen Wert zurückgibt, der den Erfolg, oder einen Fehler anzeigt, muss man das ansonsten auf meine gezeigte Weise machen.

    Ich bin nicht ganz sicher, ob ich dich richtig verstanden habe, aber im Grunde ist auch ein Zeiger das gleiche, wie alle andere Datentypen auch und er verhält sich auch so. Sprich du hast einen Wert und du kannst die Adresse bestimmen. ( &-Operator)

    Dein gezeigtes Beispiel sieht nicht nur für den Compiler so aus, sondern es ist ein Zeiger auf einen Zeiger, der auf ein int zeigt.

    int i = 2; // Wert
    int* pi = &i; // Zeiger auf int
    int** ppi = &pi; // Zeiger auf einen Zeiger, der auf ein int zeigt
    

    Hier siehst du, dass du einen Zeiger ganz genauso benutzen kannst, wie eine andere Variable. Ich denke einmal, dass dich die *-Notation verwirrt.
    Allerdings kannst du das auch verdeken, was oftmals gemacht wird, wenn man mehr, als einen Zeiger hat.

    typedef int* PI;
    typedef PI* PPI;
    
    int i = 2; // Wert
    PI pi = &i; // Zeiger auf int
    PPI ppi = &pi; // Zeiger auf einen Zeiger, der auf ein int zeigt
    

    Und dieses Spielchen kannst du natürlich noch viel weiter treiben. Im Einsatz habe ich aber noch nie mehr als einen Doppelzeiger gesehen. (Braucht es grundsätzlich auch nicht)



  • das ist soweit klar danke. ich meinte aber eben das ein normaler zeiger, von dem man die adresse selber als parameter übergibt, er wie ein **ptr angesehen wird, ohne das der zeiger je in berührung mit einem **ptr gekommen ist. und deswegen denke ich das sobald die adresse eines zeiger selber übergeben wird, es als **ptr angesehen wird, weil:

    der wert der übergeben wird ist nicht einfach ein normaler wert in dem sinne, sondern die adresse der eigentlichen zeiger-variable und da ein **ptr einen sollchen wert als **ptr sieht ist damit auch logisch, weil ein **ptr einen sollchen wert ja als zeiger aufnimmt und nicht als value ansich. sprich, die adresse des zeiger selber ist eine höhere form als der wert als die adresse die der zeiger speichert, auf die referenziert werden kann. somit ist es eiben eigentlich ein **ptr für den compiler, aber grundsätzlich ist es eben doch ein normaler pointer. hoffe es ist jetzt klarer



  • Hmm.

    void foo (int** ppi) [1]
    {..}
    
    ...
    
    int i = 2;
    int* pi = &i;
    
    foo ( &pi );[2]
    

    Also nochmal. Ein int** ist ein völliger anderer Datentyp, als ein int* und der Compiler wird das als Fehler markieren, wenn du das etwas durcheinander bringen willst.
    Bei [1] hast du eine Deklaration von einem int**, welcher bei [2], bei dem Aufruf der Funktion mit der Adresse eines int* initialisiert werden muss (sollte, da auch ein Null Zeiger übergeben werden könnte..).

    Schlussendlich ist hier ppi auch einfach ein Zeiger, abder der Datentyp ist auch wichtig. Ist es ein Zeiger auf MySuperClass, auf bool, auf int, oder auf int* ?
    Und int** beschreibt einen Zeiger, der auf einen Zeiger zeigt, der wiederum auf einen Integer zeigt.

    EDIT:
    Probier Zeiger nicht als etwas spezielles zu sehen. Schaue sie dir auch, wie einen ganz normalen Datentypen an, auf den du auch einen Zeiger erstellen kannst. Aber ich denke grundsätzlich hast du es verstanden. 🙂



  • ich versuche es ohne grossen schnickschnack 🕶

    version 1:

    - die adresse eines zeiger ist für den zeiger selber bereits die allerhöchste ebene.
    - für einen **ptr ist diese adresse dann gerade mal der wert, den er aufnehmen möchte.

    version 2:

    - ein normaler zeiger ist ein sollcher, da er eine adresse von einem speicherbereich aufnimmt. seine eigene adresse ermöglicht es ihm aber sich zu einem **ptr zu gesellen. der **ptr wiederum nimmt aber die adresse als wert auf.

    version 3:

    - wenn man mit einem zeiger arbeitet und ihn ohne vorzeichen verwendet, wird immer die adresse des speicherbereichs verwendet, nicht aber die adresse des zeiger selbst. und das ist ein *ptr. wenn man aber nun die adresse des zeiger selber verwendet (&ptr) ist dies eine ebene höher als ptr und somit sieht es für einen **ptr aus wie eine gültige wert aufnahme eines *ptr.

    =====

    bildvisualisation wäre besser :p
    ist das jetzt ein bisschen verständlicher? so oder so, vielen dank für deine bemühungen 🙂



  • eine version habe ich noch:

    version 4:

    da die eigene pointer adresse prinzipiel nicht verwendet wird, wenn man dem zeiger einen anderen zeiger zuweist oder dereferenziert, ist die eigene adresse schon fast sowas wie ich-zeige-auf-mich-selber, kann aber eine andere adresse als zeiger-verwendung speichern

    denke aber version 1-3 ist besser. :xmas2:



  • zugriffer schrieb:

    - die adresse eines zeiger ist für den zeiger selber bereits die allerhöchste ebene.

    Ich verstehe nicht ganz, was du damit meinst. 😕

    zugriffer schrieb:

    - für einen **ptr ist diese adresse dann gerade mal der wert, den er aufnehmen möchte.

    Den Wert, den ein int** aufnimmt, ist die Adresse eines int*.
    Mit diesem Wert alleine kannst du nicht viel machen, ausser ausgeben.

    int i = 2;
    int* pi = &i;
    int* ppi = &pi;
    std::cout << ppi;
    

    Das gibt dir die Adresse aus, wo pi steht.

    Allerdings kannst du ja den Zeiger dereferenzieren und dann bekommst du den Wert, auf den der Zeiger zeigt.

    int i = 2;
    int* pi = &i;
    int* ppi = &pi;
    std::cout << *ppi;
    

    Gibt dir die Adresse von i aus.

    zugriffer schrieb:

    - ein normaler zeiger ist ein sollcher, da er eine adresse von einem speicherbereich aufnimmt. seine eigene adresse ermöglicht es ihm aber sich zu einem **ptr zu gesellen. der **ptr wiederum nimmt aber die adresse als wert auf.

    Es gibt nur eine Art von Zeigern. Du musst dir klar machen, dass ein Zeiger immer die Adresse eines Objeketes beinhaltet (ob das jetzt int, bool, oder int********* ist, spielt keine Rolle).
    Das Objekt dahinter interessiert dich erst, wenn du dereferenzierst. (natürlich weisst du das schon bei der Erstellung des Zeigers, weil du ja alles angeben musst).
    Also schau dir int* als Objekt an. Dann möchtest du einen Zeiger auf dieses Objekt haben. Und bekannter weise braucht man lediglich ein * hinter den Typen zu machen, damit man den Zeiger Typen hat.

    typedef ObjektTyp* ZeigerTyp;
    

    Und jetzt wollen wir einen Zeiger auf int*.

    typedef (int*     )  * ZeigerTyp;
            (ObjetkTyp)
    

    zugriffer schrieb:

    - wenn man mit einem zeiger arbeitet und ihn ohne vorzeichen verwendet, wird immer die adresse des speicherbereichs verwendet, nicht aber die adresse des zeiger selbst. und das ist ein *ptr. wenn man aber nun die adresse des zeiger selber verwendet (&ptr) ist dies eine ebene höher als ptr und somit sieht es für einen **ptr aus wie eine gültige wert aufnahme eines *ptr.

    Ich denke, dass ich das bereits oben erklärt habe. (Siehe Ausgabe Beispiel).

    zugriffer schrieb:

    da die eigene pointer adresse prinzipiel nicht verwendet wird, wenn man dem zeiger einen anderen zeiger zuweist oder dereferenziert, ist die eigene adresse schon fast sowas wie ich-zeige-auf-mich-selber, kann aber eine andere adresse als zeiger-verwendung speichern

    Ein Zeiger kann nicht auf sich selbst zeigen. Der der Typ, den ein Zeiger aufnimmt hat immer ein * weniger. (sollte auch oben ersichtlich sein).



  • drakon schrieb:

    Ein Zeiger kann nicht auf sich selbst zeigen. Der der Typ, den ein Zeiger aufnimmt hat immer ein * weniger. (sollte auch oben ersichtlich sein).

    Ach, ist das so? 😉

    void* ptr;
    ptr = &ptr;
    

    Gruß
    Don06



  • Pointer, auf die man selbst zugreifen kann, sind einfach immer wieder schön. Es lebe C. Die C++ Referenzen sind degegen richtig langweilig.

    Schau Dir auch die auto_ptr an:
    http://www.henkessoft.de/C++/C++ Fortgeschrittene/C++_Fortgeschrittene.htm#2.9._auto_ptr



  • Don06 schrieb:

    drakon schrieb:

    Ein Zeiger kann nicht auf sich selbst zeigen. Der der Typ, den ein Zeiger aufnimmt hat immer ein * weniger. (sollte auch oben ersichtlich sein).

    Ach, ist das so? 😉

    void* ptr;
    ptr = &ptr;
    

    Gruß
    Don06

    Hehe. Hast recht. Da habe ich jetzt nicht daran gedacht. Allerdings würde es mit roher Gewalt auch mit richtigen Typen gehen. 🙂 - Da geht so ziemlich alles.

    @Erhard Henkes
    😕

    Es geht hier um das grundlegende Verständnis von Zeigern. Klar hat man viele Probleme nicht mit smart Pointern, aber wenn es jemand verstehen will, dann sollte man ihn imo nicht auf etwas anderes verweisen. Nichtsdestotrotz danke für den Querverweise. (@zugriffer : danach dich dort einlesen. :))



  • zugriffer schrieb:

    WARUM also, kann mann trotzdem noch mit "pMyClass->pMyClass2->i = 5;" darauf zugreiffen? wiegesagt, pMyClass->pMyClass2 zeigt ja auf pMyClass2 und somit dachte ich, egal wieviele zeiger es gibt, sobald der hauptzeiger, der den speicher reserviert hat, eliminiert wird, sind diese auch alle tot. was ja mein beispiel damit demonstrieren soll.

    Die ISO Norm sagt dazu: "undefined behavior". Das heißt, es kann alles möglich passieren.



  • drakon schrieb:

    zugriffer schrieb:

    - die adresse eines zeiger ist für den zeiger selber bereits die allerhöchste ebene.

    Ich verstehe nicht ganz, was du damit meinst. 😕

    damit meinte ich das keine weitere adresse für einen normalen zeiger mehr existiert. bei einem **ptr wäre diese ebene gerade mal die adresse und somit speicherbereich, wo er die adresse des *ptr speichert. der **ptr hat dann noch eine ebene höher wo dann wiederum seine adresse steht und dort ist dann auch finish.

    wiegesagt, die logik und arbeit mit zeigern ist mir schon lange bekannt. nur wollte ich eben den syntax der dahinter verborgen ist genauer verstehen. das tu ich grundsätzlich schon auch aber leider reden wir ein kleines stückchen aneinander vorbei. dies liegt natürlich leider daran das ich meine frage diesbezüglich leider nicht so formulieren konnte damit es verständlich rüber kommt. das alles im endeffekt zeiger = zeiger ist, ist schon klar. nur gibt es eben auch einen syntax der das ganze somit auch logisch beschreiben tut.

    das gleiche beispiel bei referenzen. ein beispiel dazu:

    void Function(int &ref)
    {
        ref = 123;
    }
    
    ...
    
    int *p_i = new int;
    Function(*p_i);
    delete p_i;
    

    den code zu verstehen ist das einte. sowas macht gelerntes C++ aus. den hintergrund des syntax aber auch zu verstehen, ist etwas völlig anderes. und sowas sind eben sachen wie ich bislang alle kenne und verstehe und nun möchte ich es eben auch bei zeigern/referenzen.

    in meinem beispiel kommt jeder damit zurecht, der C++ gelernt hat, selbst dann wenn er sich nie über die arbeit bezüglich des syntax gemacht hat. hier müsste man nun nähmlich wissen, das der compiler automatisch den dereferenzierungs-operator bei ref anwendet. man müsste wissen das bei der zeile:

    Function(*p_i);

    die adresse des angeforderten speicher für int übergeben wird, anstatt der wert, der hinter dem zeiger steckt, was ein normales dereferenzieren ausmachen würde. wenn man nähmlich keine referenz, sondern einen pointer übergeben würde, wäre die zeile nähmlich einfach nur:

    Function(p_i);

    und damit wird die adresse des speicherbereichs übergeben. ich glaube sogar (einw enig vom jetigen thema weg), dass sogar bei einem array mit folgender zeile:

    ++array;

    schlicht weg die adressierung wortwortlich inkrementiert wird, da die adressen der speicherstellen jeweils um eine zahl erhöt aufliegen. angenommen array[0] hat die adresse 11223340 und array[1] 11223341 dann könnte man wahrscheinlich sogar folgendes schreiben, um auf das dritte ([2]) array zu zeigen:

    array = 11223342;

    nur rein theoretisch! 🕶 ich für mich möchte wiegesagt nicht wissen wie etwas funktioniert, dass habe ich für mich gelernt. sondern vielmehr noch die paar syntax eigenschaften und wie die welt dahinter wirklich ausschaut. deswegen dieser lange redefluss bezüglich **ptr und *ptr.

    don't get panic, i'm just a human! 🤡



  • ach okay. es ist garnicht so das die adressierung der array elemente immer um eins höher wird. demnach habe ich nur teilweise recht gehabt mit dem beispiel. aber dennoch funktioniert das hier:

    int main(int argc, char* argv[])
    {
    	int *array = new int[5];
    	array[0] = 100;
    	array[1] = 200;
    	array[2] = 300;
    	array[3] = 400;
    	array[4] = 500;
    
    	array = (int*)9648372;
    
    	cout << "array[0]: " << array[0] << "\n";
    	cout << "array[1]: " << array[1] << "\n";
    	cout << "array[2]: " << array[2] << "\n";
    	cout << "array[3]: " << array[3] << "\n";
    	cout << "array[4]: " << array[4] << "\n";
    
    	delete[] array;
    	return 0;
    }
    


  • für mich wäre hier also wiederum interessant zu wissen, woher der prozessor weiss, das mit folgender anweisung:

    ++array;

    array die adresse des nächsten elements bekommt, wenn die adresserung ja nicht inkrementel ist. woher weiss der prozessor also, welche adresse gemeint ist?
    *(++array) wäre ja das gleiche wie array[n];



  • zugriffer schrieb:

    aber dennoch funktioniert das hier

    Das ist aber undefiniertes Verhalten. Gut möglich, dass du eine Zugriffsverletzung erhältst. Gerade, wenn irgendein Speicherbereich ein delete[] abkriegt, sollte es eine Heap Corruption geben.

    zugriffer schrieb:

    array die adresse des nächsten elements bekommt, wenn die adresserung ja nicht inkrementel ist. woher weiss der prozessor also, welche adresse gemeint ist?

    Inwiefern nicht inkrementell?

    Der Zeiger ist ja typisiert (in deinem Falle int ). Der Compiler weiss, dass sizeof(int) beispielsweise 4 ist. Wenn du den Zeiger inkrementierst, wird dieser um 4 Bytes nach vorne verschoben, zeigt also gerade auf das nächste Element.

    Deshalb kann man auch mit void -Zeigern keine Zeigerarithmetik durchführen.



  • achso das klingt logisch. sowas sind eben die sachen die mich im background auch interessieren.



  • Wenn du dich so sehr für Zeiger, Arrays und die ganzen Grundlagen dafür interessierst, dann lern Assembler.
    Wenn du mal einigermassen gut Assembler kannst, dann verstehst du auch automatisch was Zeiger sind, was Zeiger auf Zeiger sind, wie Arrays angelegt werden etc.



  • hustbaer schrieb:

    Wenn du dich so sehr für Zeiger, Arrays und die ganzen Grundlagen dafür interessierst, dann lern Assembler.
    Wenn du mal einigermassen gut Assembler kannst, dann verstehst du auch automatisch was Zeiger sind, was Zeiger auf Zeiger sind, wie Arrays angelegt werden etc.

    Nicht so wirklich, eigentlich reicht für sowas C++ oder besser C aus wenn man sich dann auch noch etwas mit Compilern beschäftigt (siehe Nexus letzter post), was Assembler ja nicht hat.



  • jungs, vielen dank für diese tollen beiträge. ich hoffe ich habe niemanden zu sehr strapaziert 🕶

    meine frage bezüglich des syntax bei referenzen hätte ich dennoch. wenn man folgendes schreibt:

    void Function(bool &ref)
    {
    }
    
    ...
    
    bool ref;
    Function(ref);
    

    dann weiss der compiler automatisch das er von der normalen variable ref die adresse anstelle des wertes nehmen muss. das ist doch soweit richtig oder? dann bei nachfolgendem beispiel muss ich raten:

    void Function(bool &ref)
    {
    }
    
    ...
    
    bool ref;
    bool *p_ref = &ref;
    Function(*p_ref);
    

    bei dieser art der referenz-übergabe muss man ja den dereferenzierungs-operator anwenden. wie wir aber alle wissen, hätte ein dereferenzieren normalerweise den effekt, das der wert des speicherbereiches entnommen wird. ich frage mich jetzt ob es wie folgt zu verstehen ist:

    anstatt das der compiler den wert holt und ihn übergibt, nimmt er die adresse des speicherbereichs auf den gezeigt wird. da der compiler bei referenzen automatisch den adressoperator verwendet, würde sonst die adresse der zeigervariable übergeben, demnach muss man hierfür den dereferenzierungs-operator verwenden, auch wenn nicht im klassischen sinne dereferenziert wird. kommt das hin? demnach sieht:

    Function(p_ref); für den compiler das ganze so aus:
    Function(&p_ref); was widerum falsch wäre, da dann die adresse der zeigervariable übergeben wird.

    Function(*p_ref); sieht für den compiler dann so aus:
    Function(&ref);



  • Richtig der Compiler sieht, dass die Funktion, dass eine Referenz verlangt wird und du musst dich ledigilch darum kümmern, dass ein Objekt übergeben wird. Das ist notwendig, weil es nicht möglich ist eine ungültige Referenz zu haben. Eine Referenz verweisst immer auf ein Objekt. (Was bei Zeiger ja nicht sein muss).

    Function(*p_ref); // ist gleichbedeutend mit:
    Function(ref);
    

    Wie der Compiler schlussendlich eine Referenz umsetzt ist nicht fest geschrieben. Normalerweise wird es aber wahrscheinlich über Zeiger gehen.


Anmelden zum Antworten