Zugriff auf zerstörtes Objekt möglich



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



  • drakon schrieb:

    Das ist notwendig, weil es nicht möglich ist eine ungültige Referenz zu haben

    Möglich ist es schon, bloss ist es "nicht OK", d.h. es stellt einen Programmierfehler dar, und das Verhalten ist nicht definiert. Ich denke das ist das was du gemeint hast, ich wollte es nur nochmal etwas genauer schreiben.

    Beispiel:

    void foo_2(int& ref)
    {
       ref = 0;
    }
    
    void foo_1(int* ptr)
    {
       foo_2(*ptr); // <- das ist noch OK, foo1 verlangt aber so dass ptr nicht NULL sein darf
                    // (Aufrufer von foo_1 müssten sich einfach daran halten, dann wäre alles OK)
    }
    
    void bar()
    {
        foo_1(0); // <- Programmierfehler ist hier. Grundsätzlich ist es OK NULL als Zeiger zu übergeben,
                  // bloss eben nicht an foo_1.
                  // In diesem Fall entsteht dann in foo_1 die ungültige Referenz
    }
    

    zugriffer schrieb:

    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:

    Es kommt inetwa hin. Es wird allerdings sehrwohl "im klassischen Sinn" dereferenziert (zumindest wenn man über C++ redet), bloss ... pfuh, das ist schwer zu erklären.

    Man könnte sagen wenn du "*p" schreibst, befielst du dem Compiler nicht den Wert auf den "p" Zeigt zu lesen, sondern du sagst ihm mit "*p" bloss "ich meine nichtmehr p selbst, sondern das worauf p zeigt". Gelesen muss dabei noch nicht werden. Erst wenn der "Wert" des Ausdrucks *p "verwendet" wird muss gelesen werden.

    Daher kannst du den Ausdruck "*p" auch an eine Funktion übergeben, die eine Referenz erwartet, da "das worauf p zeigt" nicht irgendein namenloses Zwischenergebnis ist, sondern ein "identifizierbares etwas". (Solche "namenlosen Zwischenergebnisse" dürfen nämlich nicht als "Ziel" für Referenzen verwendet werden, sondern nur als Ziel für "const Referenzen", also nur für Referenzen über die man das referenzierte Objekt nicht verändern kann -- also z.B. "const int&" statt "int&")

    Im Prinzip rede ich hier über den Unterschied zwischen lvalues und rvalues, wenn du magst kannst du ja im Standard nachlesen 🙂
    Ist leider etwas schwer verständlich und sehr abstrakt formuliert, aber ich wüsste keine wirklich gute, nicht-abstrakte Beschreibung dafür was eine lvalue bzw. rvalue ist.

    Die beste (vereinfachende) Beschreibung ist: eine rvalue darf nur auf der rechten Seite einer Zuweisung stehen, eine lvalue auch auf der linken. Das verdeutlicht vielleicht auch warum bei "*p" noch nix gelesen werden muss:

    void foo()
    {
        int i = 0;
        int* p = &i;
        *p = 23; // *p ist eine lvalue, darf daher auf der linken seite stehen
                 // hier sollte auch klar werden was ich meinte: *p bedeutet nicht dass der Wert gelesen wird,
                 // denn in dem Fall verwenden wir *p ja um einen neuen Wert zu *schreiben*,
                 // was ja logischerweise nicht erfordert den alten Wert vorher zu lesen
    
                 // EDIT: einfacher gesagt: *p "identifiziert" ein "Objekt", und zwar nichtmehr das Objekt "p" selbst, sondern das Objekt worauf "p" zeigt. Man kann dieses Objekt dann "auslesen", muss man aber nicht :)
    
        std::cout << i; // druck 23
    
        int x;
        x = (*p) + 0; // "(*p) + 0" ergibt den gleichen "Wert" wie "*p", nämlich hier 23, ist aber eine rvalue.
                      // Hier OK, da es auf der rechten Seite steht.
        // x ist jetzt auch 23
    
        ((*p) + 0) = 5; // nicht OK, "(*p) + 0" ist eine rvalue, und darf daher nicht auf der linken Seite stehen
    }
    

    ----

    Ich hoffe das hat jetzt mehr geholfen als es dich verwirrt hat :xmas1:



  • alles klar danke :xmas1:


Anmelden zum Antworten