Zugriff auf zerstörtes Objekt möglich



  • vielen dank euch allen. eine frage noch zur aussage von xerox_

    Dir sollte auch klar sein das delete nicht den Speicherbereich des Objekts löscht bzw. jedes Byte davon auf NULL setzt sondern es wird lediglich dem Kernel (also Windows) gesagt das der Speicherbereich nicht mehr benötigt wird und wieder verwendet werden kann

    das ist interessant. ich dachte das dass ganze ein bisschen anderst abläuft. aus vielerlei gründen. unteranderem weil C++ plattformunabhängig ist und es auch mit keinem system in verbindung gebracht wird. gut, dass tut es mit deiner aussage ja auch nicht, sowas regelt wohl der C++ standard. der new und delete operator unterliegen ja z.B. einer speziellen regelung, anderst als bei anderen sprachfeatures.

    ich dachte eigentlich bislang das der delete operator dem prozessor mitteilt, das speicher an adresse X freigegeben werden muss. ich für mich weiss, dass grundsätzlich assembler nichts mit dem betriebssystem zu tun hat, ein betriebssystem aber natürlich seine finger im spiel hat wenn applikationen ausgeführt werden. und da der assembler teil für ein delete meiner meinung nach ja nichts mit einem eingreifen auf system ebene zu tun haben sollte, war für mich eigentlich eben der gedanke, dass dies so geschehen würde die gedacht.

    dann habe ich aber gleich eine frage zur bitmanipulation. C++ ist ja dafür bekannt, das man sehr hardware nahe programmierung ausschöpfen kann. für mich ist die bitmanipulation z.B. der grösste teil davon. wird dies komplett ohne systemeingriff getan? angenommen ich verwende sämtliche bitwise operatoren (|, &, ^, ~ etc.). ist das dann ein direktes arbeiten von:

    C++ -> assembler -> cpu -> arbeitsspeicher?



  • ergenzung: kann man irgendwo nachlesen wie der new und delete operator komplett in assembler ausschaut? das würde mich wirklich mal interessieren. leider kann ich die assembler files, die mein C++ compiler mir ausgibt nicht dazu verwenden um schlauer zu werden. dort wird hald eben new und delete einfach als extern deklariert, mehr nicht. sämtliche anderen C++ sprachfeatures sind in direktem assembler code zu sehen. was eben sehr interessant ist. man muss erst einmal drauf kommen, das der geschrieben C++ code nicht umbedingt auch so in assembler vorliegt. 😋

    z.B. eine deklarationen einer klasse und anschliesende definition der methoden ergibt noch lange keinen assembler code. im nachhinein ja auch logisch, weil was soll der prozessor machen? erst wenn man die klasse verwendet ergibt sich der eigentliche assembler dazu. bis ich sowas gesehen habe, ging ich immmer der annahme das selbst definitionen in assembler übersetzt werden. aber erst das benutzen und das zusammengehörige verhalten macht den eigentlichen code aus. grundsätzlich sollte ein sauberer C++ code ja auch so aggieren, aber dennoch, sehr interessant :xmas1:



  • zugriffer schrieb:

    ergenzung: kann man irgendwo nachlesen wie der new und delete operator komplett in assembler ausschaut? das würde mich wirklich mal interessieren. leider kann ich die assembler files, die mein C++ compiler mir ausgibt nicht dazu verwenden um schlauer zu werden. dort wird hald eben new und delete einfach als extern deklariert, mehr nicht.

    Versuch es vielleicht mal mit einem anderen Compiler. Der Assemblercode kann je nach Plattform, Architektur und Compiler unterschiedlich sein.



  • okay danke. noch eins: darum heisst es dereferenzierung wenn man den wert eines zeiger ändert und nicht wenn man den zeiger ändert? also:

    dereferenzierung heisst:
    *Pointer = *Pointer2;

    warum heisst folgendes nicht dereferenzierung:
    Pointer = Pointer2;

    so zeigt Pointer ja auf einen anderen speicherbereich. macht der begriff dereferenzierung hier nicht mehr sinn? eine referenz ist ja nichts anderes als ein gültiger zeiger. ein gültiger zeiger heisst nichts anderes als das er auf einen reservierten speicherbereich zeigt. und da dereferenzieren ja eine referenz ändert.... 😕



  • Dereferenzieren heisst die Referenz (also die Indirektion) entfernen. Also direkten Zugriff auf das Objekt gewähren. Dafür wird der operator* verwendet (bei Membern auch operator-> ).

    zugriffer schrieb:

    darum heisst es dereferenzierung wenn man den wert eines zeiger ändert und nicht wenn man den zeiger ändert?

    Du drückst dich unklar aus. Der Wert eines Zeigers ist eine Adresse auf einen Speicherbereich. Wenn man diesen Wert ändert, ändert man auch den Zeiger. Wenn man den Wert des verwiesenen Speicherbereichs ändert, ist das etwas anderes.

    zugriffer schrieb:

    und da dereferenzieren ja eine referenz ändert.... 😕

    Auch hier scheinst du sehr verwirrt zu sein. Vergiss erst mal die C++-Referenz, die hat hiermit eigentlich nicht viel zu tun. Referenzen können nämlich weder geändert werden noch muss man sie explizit dereferenzieren.

    Also nochmals: Du dereferenzierst einen Zeiger, indem du seinem Verweis folgst und auf den verwiesenen Speicherbereich zugreifst.

    int var = 5;
    int zpx = 3002;
    int* ptr = &var;      // ptr ist ein Zeiger, der auf die int-Variable var zeigt.
    *ptr = 7;             // ptr wird dereferenziert, der Wert von var wird auf 7 gesetzt.
    ptr = &zpx;           // ptr wird geändert, ohne dereferenziert zu werden. Er zeigt nun auf etwas anderes (zpx).
    


  • danke. das ist mir soweit schon klar. ich gebe zu meine formulierung war nicht die beste. 🙂 ich finde es nur merkwürdig das wenn man den speicherinhalt, auf den ein zeiger verweisst, verändert, dies dann dereferenzieren heisst. wie nennt man den sowas:

    ptr2 = ptr2;

    referenzieren gibt es ja nicht :). und wäre sowieso falsch weil eben mit diesem beispiel wird der zeiger selbst geändert. gibt es einen begriff für für die zuweisung eines zeiger an einen anderen zeiger?

    also, ohne indirektionsoperator ist es kein dereferenzieren, mit indirektionsoperator ist es ein dereferenzieren. ich frage mich blos warum es nicht genau umgekehrt wer fall ist. woher kommt der begriff "dereferenzieren" sonst noch? kann man den begriff mit einem real world beispiel verknüpfen?

    int var1 = 5;
    int var2 = 10;
    var1 = var2;

    ist ja auch kein dereferenzieren und doch ist es genau das gleiche: wert an wert zuweisen. wenn das nun aber auch dereferenzierung bezeichnet wird, dann ist es okay. dann wurde einfach für die schreib/lese zugriffe der begriff dereferenzierung genommen.



  • darf ich noch zwei fragen stellen? 🕶

    wie bekommt man eigentlich die adresse des zeiger selber? &ptr liefert hierbei ja die adresse auf die der zeiger zeigt, nicht aber die adresse des zeiger selber.

    und wozu dienen zeiger auf zeiger? ich glaube dann habe ich es endlich 🕶



  • @zugriffer:

    Der Assembler-Code für new oder delete würde dir garantiert nichts sagen. Ist meist etwas kompliziert und reichlich unübersichtlich. Was new/delete macht ist auch nicht ganz richtig beschrieben worden. Ob, und wenn, wo, ein Betriebssystem da mitspielt ist im Standard z.B. nicht definiert.
    "new" gibt dir einfach eine Adresse die du verwenden darfst, und mit "delete" teilst du dem System mit dass du die Adresse nichtmehr benötigst. Zusätzlich "initialisiert" new noch das Objekt, d.h. der Konstruktor wird aufgerufen, und delete "zerstört" das Objekt, d.h. der Destruktor wird aufgerufen.

    Wenn du auf ein Objekt nach "delete" noch zugreifst verletzt du damit 2 "Übereinkommen":

    1. Der Destruktor des Objekts wurde ausgeführt, und ein Objekt das zerstört wurde "existiert nichtmehr", d.h. man darf auch nicht darauf zugreifen
    2. Du hast dem System mitgeteilt dass du den Speicherbereich nichtmehr verwendest, d.h. du darfst das auch nichtmehr tun

    Vonwegen "Dereferenzieren" - damit bezeichnet man den Vorgang wenn man quasi einer Referenz zum referenzierten Objekt folgt. Eine Referenz wäre sowas wie ne Adresse (Strasse + Hausnummer), und das referenzierte Objekt wäre ein Haus. Wenn ich mir die Strasse + Hausnummer angucken, und dann dorthingehe, dann habe ich die Adresse "dereferenziert". Ob ich jetzt auf das Haus zugreife, das angucke, reingehe etc. ist dabei egal.

    D.h. folgendes nennt man dereferenzieren:

    int x = 0;
    int* p = &x;
    
    *p; // <- hier wird p dereferenziert -- auf das "Haus" zuzugreifen ist nicht nötig damit man es als Dereferenzieren bezeichnet
    

    Wenn ich dagegen die alte Strasse + Hausnummer weglösche, und eine neue hinschreibe, dann wäre das eine einfache Zuweisung. Denn dazu muss ich ja weder zum alten noch zum neuen Haus hingehen.



  • zugriffer schrieb:

    darf ich noch zwei fragen stellen? 🕶

    wie bekommt man eigentlich die adresse des zeiger selber? &ptr liefert hierbei ja die adresse auf die der zeiger zeigt, nicht aber die adresse des zeiger selber.

    Nein, "&ptr" liefert die Adresse von "ptr", und wenn "ptr" ein Zeiger ist, dann ist das die Adresse des Zeigers. Die Adresse auf die "ptr" zeigt, ist einfach "ptr".

    und wozu dienen zeiger auf zeiger? ich glaube dann habe ich es endlich 🕶

    Wozu dienen Zeiger überhaupt? Wenn du das beantworten kannst, dann hast du auch die Antwort auf deine Frage.
    Zeiger auf Zeiger sind dafür gut, damit man mit Zeigern auch alles das machen kann, was man mit Hilfe von Zeigern mit Nicht-Zeigern machen kann 😃
    (OK, das muss man jetzt nicht verstehen, auch wenn der Satz IMO korrekt ist)

    Also nochmal anders: Zeiger verwendest du z.B. wenn du einer Funktion die Adresse von irgendwas mitgeben willst, damit diese Funktion dieses irgendwas ändern kann.
    OK. Jetzt kann es aber vorkommen, dass dieses irgendwas selbst ein Zeiger ist. In dem Fall hast du dann einen Zeiger auf einen Zeiger.

    Wenn man modernes C++ programmiert, sind die Fälle, in denen man Zeiger auf Zeiger braucht, allerdings recht selten. Sehr selten. Kann mich nicht erinnern wann das letzte mal war dass ich einen gebraucht hätte.



  • guten morgen hustebaer und danke für deine beiträge. nun, was zeiger ansich sind und wie man sie verwendet ist mir schon klar. nur eben zeiger auf zeiger (int **ptr) ergibt mir ohne gutes beispiel noch keinen sinn. man kann damit ja auf einen zeiger zugreifen der selbst auf einen speicherbereich zeigt.

    Jetzt kann es aber vorkommen, dass dieses irgendwas selbst ein Zeiger ist. In dem Fall hast du dann einen Zeiger auf einen Zeiger

    und eben, was soll das bringen? mit einem normalen zeiger den ich als parameter übergebe, kann ich den zeiger selbst ändern oder nur den wert der im speicher steht. wozu also einen doppel-moppler zeiger?



  • Was new/delete macht ist auch nicht ganz richtig beschrieben worden. Ob, und wenn, wo, ein Betriebssystem da mitspielt ist im Standard z.B. nicht definiert.

    angenommen wir schreiben ein eigenes betriebssystem in C++ und assembler. dann existiert ja noch kein "system" also muss new und delete ja irgendwie unabhängig aggieren, ergo komplett in assembler ersichtlich sein.

    "new" gibt dir einfach eine Adresse die du verwenden darfst, und mit "delete" teilst du dem System mit dass du die Adresse nichtmehr benötigst.
    

    auch hier: wenn du schreibst, "gibt mir eine adresse" was eminst du damit genau, bzw. wer gibt mir die adresse? der prozessor oder das betriebssystem? wenn letzteres kann ich nicht verstehen wie linux/windows und co. überhaupt existieren können ;). ich glaube eben das im höchstfall das betriebssystem dazu zuständig ist, das speichermanagement zu kontrollieren. der new operator ansich aber kommuniziert vielmehr mit dem prozessor selber über assembler befehle die den new operator repräsentieren. weil wiegesagt, new und delete kann man auch benutzen wenn man sein eigenes OS schreibt. hierbei muss man dann auf tiefer hardware ebene mit assembler teile zusammenarbeiten.



  • ich möchte nochmals schnell was einwerfen, das löst vielleicht endlich mein komisches gefühl zum begriff dereferenzieren.

    gibt es diesen begriff explizit für den dafür vorgesehenen vorgang? ich habe bei diesem wort einfach das gefühl das ich es mit etwas anderem assoziieren kann und deswegen kommt es mir so komisch vor. als ich früher angefangen habe zu lernen, kamen begriffe wie allokieren, instanziieren, polymorphie usw. das waren begriffe die ich nicht verbinden konnte und somit war für mich gleich klar, "aha, okay so nennt man also im fachchargon diese vorgesehnsweise" versteht ihr? bei dem begriff dereferenzieren ist es bislang nicht ganz so weil er mir eben ein bisschen vertraulich in aber einem völlig anderem zusammenhang vorkommt.

    gibt es den fachausdruck dereferenzierung also nur im sinne der programmierung und im sinne der hier genannten beispiele? dann würde eigentlich alles geklärt sein. und wenn nein, von was wird der begriff abgeleitet? was ja meist der fall ist. genau wie bei polymorphie (griechisch πολυμορφία, „die Vielgestaltigkeit“)



  • Bulli schrieb:

    Wenn ich dein pSelf und pAdr richtig verstanden habe, dann müsste es eher so aussehen:

    (pSelf = die adresse der zeigervariable selber, pAdr = die adresse des zu verweisenden zeiger)
    
    pPointer1 = new int; //pSelf: 0000001A, pAdr: 0000001B
    pPointer2 = new int; //pSelf: 0000002A, pAdr: 0000002B
    
    pPointer2 = pPointer1; //(pPointer2) pSelf: 0000002A, pAdr: 0000001B
    
    delete pPointer1;
    pPointer1 = NULL;
    
    //und jetzt kommt meine vermutung
    /* (pPointer1) pSelf: 0000001A, pAdr: 00000000
       (pPointer2) pSelf: 0000002A, pAdr: 0000001B */
    
    //und dann
    
    delete pPointer2; //würde eine exception werfen...
    pPointer2 = NULL; //das sollte aber den unten beschriebenen teil ausmachen
    
    /* (pPointer1) pSelf: 0000001A, pAdr: 00000000
       (pPointer2) pSelf: 0000002A, pAdr: 00000000 */
    

    habe es nun getestet. wir haben beide nicht recht. die adresse des zeiger selber wird nähmlich auf NULL gesetzt. nicht die adresse auf die der zeiger zeigt.

    int *p_a = new int;
    
    	delete p_a;
    	p_a = NULL;
    
    	cout << "p_a: " << p_a << "\n";
    	cout << "&p_a: " << &p_a << "\n";
    

    ausgabe:

    p_a: 0
    &p_a: 1245064

    somit stimmt dein beispiel nicht und meins auch nicht. ich dachte ja das der zeiger selber 0 ist und der speicherbereich worauf er zeigt. du meintest das der speicherbereich worauf er zeigt 0 ist, der zeiger selber aber nicht. es ist aber so das der zeiger selber 0 ist und der speicherbereich nicht. dieser (mit adresse 1245064) ist aber dennoch natürlich wieder frei verfügbar.



  • verflucht nochmal! 🤡 sorry du ansich hast recht. ich habe es verwirrenderweise verwechselt. die zeiger variable p_a enthält ja die adresse des speicherbereichs auf den gezeigt wird. mit &p_a gibt es die adresse der zeiger variable. soo!! 😮 🕶

    damit hast du recht und alles ist klar was das angeht.



  • zugriffer schrieb:

    guten morgen hustebaer und danke für deine beiträge. nun, was zeiger ansich sind und wie man sie verwendet ist mir schon klar. nur eben zeiger auf zeiger (int **ptr) ergibt mir ohne gutes beispiel noch keinen sinn. man kann damit ja auf einen zeiger zugreifen der selbst auf einen speicherbereich zeigt.

    Jetzt kann es aber vorkommen, dass dieses irgendwas selbst ein Zeiger ist. In dem Fall hast du dann einen Zeiger auf einen Zeiger

    und eben, was soll das bringen? mit einem normalen zeiger den ich als parameter übergebe, kann ich den zeiger selbst ändern oder nur den wert der im speicher steht. wozu also einen doppel-moppler zeiger?

    okay ich glaube ich verstehe jetzt sogar zeiger auf zeiger. ein beispiel:

    int main(int argc, char* argv[])
    {
    	int a, b;
    	int *p_a, **pp_a;
    
    	p_a = &a;
    	pp_a = &p_a;
    
    	cout << "p_a: " << p_a << "\n";
    	cout << "&p_a: " << &p_a << "\n";
    
    	cout << "pp_a: " << pp_a << "\n";
    	cout << "&pp_a: " << &pp_a << "\n";
    
    	**pp_a = 20;
    	*pp_a = &b;
    	**pp_a = 40;
    
    	cout << "a: " << a << "\n";
    	cout << "b: " << b << "\n";
    
    	return 0;
    }
    

    man könnte damit z.B. weitere speicherbereiche manipulieren, ohne einen bereits vorhandenen zeiger (hier p_a) zu ändern. klingt das logisch?



  • anderst gesagt: mit einem zeiger auf einen zeiger kann man einen anderen zeiger manipulieren und auf den speicherinhalt dereferenzieren, auf den der zeiger zeigt. für ein passendes beispiel bin ich jetzt auch zu müde es macht aber auf jedenfall sinn.



  • zugriffer schrieb:

    anderst gesagt: mit einem zeiger auf einen zeiger kann man einen anderen zeiger manipulieren und auf den speicherinhalt dereferenzieren, auf den der zeiger zeigt. für ein passendes beispiel bin ich jetzt auch zu müde es macht aber auf jedenfall sinn.

    Ja, das stimmt so. Man kann das z.B nutzen, um einen Speicherbereich von einer Funktion holen zu lassen. Du kannst dort nicht einfach einen Zeiger übergeben, da der dann ja überschrieben wird. Also musst du einen Zeiger auf den Zeiger übergeben, der dann dereferenziert wird und Speicher zugewiesen werden kann. Und dann hast du den Speicher.

    void alloc_int ( int** ppi)
    {
     (*ppi) = new int;
    }
    ..
    int* pi = 0;
    alloc_int ( &pi );
    
    delete pi;
    


  • aber würde das nicht auch so gehen?

    int *alloc_int () 
    { 
     return new int; 
    } 
    .. 
    int* pi = alloc_int (); 
    
    delete pi;
    

    eigentlich ungefär das selbe. hier wird ja nichts überschrieben.



  • 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)


Anmelden zum Antworten