Speicher freigeben, durch aufeinanderfolgende Zeiger



  • coder++ schrieb:

    Danke.

    Aber dennoch seltsam.
    Denn wenn ich so den Speicher einer Klasse freigeben will, kann ich durch den 1. Zeiger immernoch auf die Elemente zugreifen.

    ja nein!

    Das kann funktionieren, muss aber nicht. D.h. in den allermeisten Fällen wird es nicht funktionieren, bei dir ists grad nur ein Zufall, wenn man so will.

    Die Laufzeitumgebung könnte delete so implementiert haben, dass nur aus einer Tabelle/Liste ein Eintrag entfernt wird (oder umgekehrt eventuell hinzugefügt wird), aber mehr nicht passiert. Dann kannst du, wenn du auf diesen (jetzt wieder freien) Speicher zugreifst, die alten Daten noch sehen.
    Andere Laufzeitumgebungen könnten aber auch diesen Speicher auf einen Initialzustand (z.B. alles mit 0 füllen) zurücksetzen und dann wirst du da nichts mehr rauslesen können.

    Aber um es kurz zu machen, solche "Dangling Pointers" sind der Quell vieler Fehler und unbedingt zu vermeiden. Stichwort RAII und vor allem sauber geklärte Besitzverhältnisse von Ressourcen!



  • coder++ schrieb:

    Denn wenn ich so den Speicher einer Klasse freigeben will, kann ich durch den 1. Zeiger immernoch auf die Elemente zugreifen.

    Das ist purer Zufall und kann beim nächsten Programmstart schon ganz anders aussehen. "delete" bedeutet lediglich, dass der Speicherbereich freigegeben wird in dem Sinne, dass er wieder neu alloziiert werden kann. Solange das nicht passiert, ist es durchaus möglich, dass dort noch die Elemente der gelöschten Klasse befinden.



  • Argh!
    Ich hab ja völlig vergessen, dass der Speicher selber nicht verändert wird.
    Dann hab ich mir ja völlig unnötig Sorgen um ein Speicherleck gemacht.

    Nunja, dann werd ich wohl anders testen müssen, ob das Objekt noch existiert.

    Danke an euch beide 🙂
    Edit: Euch vier.



  • coder++ schrieb:

    Danke.

    Aber dennoch seltsam.

    Ich schreibe die Adresse meines Hauses auf zwei Zettel. Ich verkaufe das Haus, das auf dem ersten Zettel genannt wird. Gehört mir dann noch das Haus, das auf dem zweiten Zettel steht?

    coder++ schrieb:

    Denn wenn ich so den Speicher einer Klasse freigeben will, kann ich durch den 1. Zeiger immernoch auf die Elemente zugreifen.

    Nein, kannst du nicht. Du beobachtest undefiniertes Verhalten, was zufällig so aussieht als wären da noch Elemente.



  • coder++ schrieb:

    Nunja, dann werd ich wohl anders testen müssen, ob das Objekt noch existiert.

    Häh? Wenn Du es gelöscht/freigegeben hast, existiert es nicht mehr. Das musst Du schon selbst nachhalten, das ist halt die Konsequenz, wenn man rohe Zeiger benutzt.



  • Hier noch ein Tool (Windows), welches dir hilft Memory-Leaks zu finden:
    https://vld.codeplex.com/

    Für Linux gibt es natürlich ähnliche Tools.



  • coder++ schrieb:

    Nunja, dann werd ich wohl anders testen müssen, ob das Objekt noch existiert.

    Man kann nicht testen ob ein Objekt noch existiert. Man muss es wissen.
    Also du kannst nen Zeiger auf das Objekt haben, und es dann zur Konvention machen dass du den Zeiger immer auf NULL setzt nachdem du das Objekt gelöscht hast.
    So lange dann keine weiteren Zeiger auf das Objekt existieren kannst du dadurch natürlich indirekt "testen" ob das Objekt noch existiert, indem du testest ob der Zeiger NULL ist oder nicht.

    Gibt es aber weitere Zeiger, die du beim Löschen des Objekts nicht NULL setzen kannst (weil du z.B. nicht weisst wo überall solche Zeiger sein könnten), dann hast du ein Problem.



  • ps:
    Google mal nach "programming ownership" (ohne die "").



  • theta schrieb:

    Hier noch ein Tool (Windows), welches dir hilft Memory-Leaks zu finden:
    https://vld.codeplex.com/

    Für Linux gibt es natürlich ähnliche Tools.

    Danke, werd ich mir mal anschauen.

    hustbaer schrieb:

    coder++ schrieb:

    Nunja, dann werd ich wohl anders testen müssen, ob das Objekt noch existiert.

    Man kann nicht testen ob ein Objekt noch existiert. Man muss es wissen.
    Also du kannst nen Zeiger auf das Objekt haben, und es dann zur Konvention machen dass du den Zeiger immer auf NULL setzt nachdem du das Objekt gelöscht hast.
    So lange dann keine weiteren Zeiger auf das Objekt existieren kannst du dadurch natürlich indirekt "testen" ob das Objekt noch existiert, indem du testest ob der Zeiger NULL ist oder nicht.

    Gibt es aber weitere Zeiger, die du beim Löschen des Objekts nicht NULL setzen kannst (weil du z.B. nicht weisst wo überall solche Zeiger sein könnten), dann hast du ein Problem.

    Danke.
    Das hab ich schon begriffen. Mit dem testen meinte ich eher, die Addressen aus allen Zeiger mit den gleichen Addressen entfernen.
    Achja, dachtest du dabei an soetwas: http://dl.acm.org/citation.cfm?id=582440
    ?


  • Mod

    coder++ schrieb:

    Das hab ich schon begriffen. Mit dem testen meinte ich eher, die Addressen aus allen Zeiger mit den gleichen Addressen entfernen.
    Achja, dachtest du dabei an soetwas: http://dl.acm.org/citation.cfm?id=582440
    ?

    Der Trick ist, dass so ein Fall nie vorkommen kann, wenn du bloß halbwegs modernes C++ nutzen würdest, anstatt von Hand mit Pointern rum zu frickeln.

    99% der typischen Anwendungsfälle manueller Speicherverwaltung kannst du mit den Containerklassen der Standardbibliothek erschlagen. Die verbliebenen 50% der Anwendungsfälle deckst du mit den Smartpointern der Standardbibliothek ab. Gerade deine aktuelle Beschreibung klingt so, als wolltest du shared_pointer, eventuell im Zusammenspiel mit weak_pointer. Das heißt nicht, dass das die beste Lösung ist, es ist nur was du oben beschreibst. Wie schon gesagt, ist zu 99% eine der Containerklassen besser geeignet. Bleiben noch 0.5% übrig für die ganz speziellen Fälle. Auch hier benutzt du keine rohen Zeiger, sondern du programmierst dir eine Klasse nach dem Vorbild der Container/Smartpointer, auf deine ganz speziellen 0.5% Anforderungen angepasst. Das heißt insbesondere, dass du RAII umsetzt. Das ist der wichtigste Punkt von allen und sorgt, wie oben schon gesagt, dass Fehler bei der Freigabe überhaupt gar nicht auftreten können*.

    *: Gleich kommen irgendwelche Klugscheißer mit hochgradig konstruierten Sprachvergewaltigung, um diese absolute Aussage zu widerlegen. Irgendwas mit reinterpret_casts, manuellen Destruktoraufrufen und Exceptions im Destruktor. Klar kann man sich absichtlich so dumm anstellen, dass man irgendeine Ausnahme findet. Aber unter normalen Umständen ist es nicht mehr möglich, etwas falsch zu machen, wenn man sich an die obigen Prinzipien hält.



  • SeppJ schrieb:

    coder++ schrieb:

    Das hab ich schon begriffen. Mit dem testen meinte ich eher, die Addressen aus allen Zeiger mit den gleichen Addressen entfernen.
    Achja, dachtest du dabei an soetwas: http://dl.acm.org/citation.cfm?id=582440
    ?

    Der Trick ist, dass so ein Fall nie vorkommen kann, wenn du bloß halbwegs modernes C++ nutzen würdest, anstatt von Hand mit Pointern rum zu frickeln.

    99% der typischen Anwendungsfälle manueller Speicherverwaltung kannst du mit den Containerklassen der Standardbibliothek erschlagen. Die verbliebenen 50% der Anwendungsfälle deckst du mit den Smartpointern der Standardbibliothek ab. Gerade deine aktuelle Beschreibung klingt so, als wolltest du shared_pointer, eventuell im Zusammenspiel mit weak_pointer. Das heißt nicht, dass das die beste Lösung ist, es ist nur was du oben beschreibst. Wie schon gesagt, ist zu 99% eine der Containerklassen besser geeignet. Bleiben noch 0.5% übrig für die ganz speziellen Fälle. Auch hier benutzt du keine rohen Zeiger, sondern du programmierst dir eine Klasse nach dem Vorbild der Container/Smartpointer, auf deine ganz speziellen 0.5% Anforderungen angepasst. Das heißt insbesondere, dass du RAII umsetzt. Das ist der wichtigste Punkt von allen und sorgt, wie oben schon gesagt, dass Fehler bei der Freigabe überhaupt gar nicht auftreten können*.

    *: Gleich kommen irgendwelche Klugscheißer mit hochgradig konstruierten Sprachvergewaltigung, um diese absolute Aussage zu widerlegen. Irgendwas mit reinterpret_casts, manuellen Destruktoraufrufen und Exceptions im Destruktor. Klar kann man sich absichtlich so dumm anstellen, dass man irgendeine Ausnahme findet. Aber unter normalen Umständen ist es nicht mehr möglich, etwas falsch zu machen, wenn man sich an die obigen Prinzipien hält.

    Ich benutze auch die Container-Klassen.
    Aber ich muss ein Element eines Objektes ständig "mitverfolgen".
    Da fiel mir als erstes ein Zeiger auf das Objekt ein.
    Ich schau mal, ob ich einen besseren Weg finde.
    Danke.



  • coder++ schrieb:

    Das hab ich schon begriffen.

    Gut. Begreifen viele nämlich nicht, bzw. erst sehr spät.

    coder++ schrieb:

    Mit dem testen meinte ich eher, die Addressen aus allen Zeiger mit den gleichen Addressen entfernen.

    Dazu kann man sich was basteln, wenn man es denn wirklich braucht.
    Wenns nicht threadsafe sein muss sollte ein shared_ptr<shared_ptr<Foo>> reichen.

    coder++ schrieb:

    Achja, dachtest du dabei an soetwas: http://dl.acm.org/citation.cfm?id=582440
    ?

    Nö. Eher an single ownership vs. shared ownership.
    http://stackoverflow.com/questions/13852710/single-vs-shared-ownership-meaning

    coder++ schrieb:

    Aber ich muss ein Element eines Objektes ständig "mitverfolgen".
    Da fiel mir als erstes ein Zeiger auf das Objekt ein.

    Wenn du etwas genauer beschreibst was ein Element eines Objektes ständig "mitverfolgen" bedeutet könnte dir vielleicht jmd. nen konkreteren Tip geben.



  • Nimm einfach shared_ptr. Ist zwar hier ziemlich verhasst aber das Ding macht in den meisten Fällen das Richtige.



  • hustbaer schrieb:

    Nö. Eher an single ownership vs. shared ownership.
    http://stackoverflow.com/questions/13852710/single-vs-shared-ownership-meaning

    Danke, seh ich mir an.

    hustbaer schrieb:

    Wenn du etwas genauer beschreibst was ein Element eines Objektes ständig "mitverfolgen" bedeutet könnte dir vielleicht jmd. nen konkreteren Tip geben.

    Im Grunde genommen, will ich das ein Objekt ein anderes verfolgt(also im dreidimemsionalen Raum). Beide Objekte sind auch im Code zwei verschiedene Objekte.
    Also dachte ich, ich könnte einfach die Addresse des zu verfolgenden Objektes in einen Zeiger laden, um so dann ständig die Position auszulesen.

    hustbaer schrieb:

    Dazu kann man sich was basteln, wenn man es denn wirklich braucht.
    Wenns nicht threadsafe sein muss sollte ein shared_ptr<shared_ptr<Foo>> reichen.

    Ethon schrieb:

    Nimm einfach shared_ptr. Ist zwar hier ziemlich verhasst aber das Ding macht in den meisten Fällen das Richtige.

    Okay, danke.



  • coder++ schrieb:

    Ich benutze auch die Container-Klassen.
    Aber ich muss ein Element eines Objektes ständig "mitverfolgen".
    Da fiel mir als erstes ein Zeiger auf das Objekt ein.
    Ich schau mal, ob ich einen besseren Weg finde.
    Danke.

    Das hört sich für mich nach Observer-Pattern an.



  • coder++ schrieb:

    hustbaer schrieb:

    Wenn du etwas genauer beschreibst was ein Element eines Objektes ständig "mitverfolgen" bedeutet könnte dir vielleicht jmd. nen konkreteren Tip geben.

    Im Grunde genommen, will ich das ein Objekt ein anderes verfolgt(also im dreidimemsionalen Raum). Beide Objekte sind auch im Code zwei verschiedene Objekte.
    Also dachte ich, ich könnte einfach die Addresse des zu verfolgenden Objektes in einen Zeiger laden, um so dann ständig die Position auszulesen.

    Observer-Pattern wurde ja schon genannt.
    Zwei Observer-Pattern Varianten:

    1. Die Position des Objekts ist "observable" - d.h. das Objekt benachrichtigt andere über Positionsänderungen.
    2. Die Lebenszeit des Objekts ist "observable" - d.h. das Objekt benachrichtigt andere wenn es zerstört wird.
      1 und 2 kann man natürlich auch kombinieren.

    Ansonsten bietet sich shared_ptr an.

    Wenn du willst dass der Verfolger das Ziel (den Verfolgten) am Leben hält, dann bekommt der Verfolger nen shared_ptr<Ziel> , und fertig.
    Das Ziel wird dann durch den Verfolger am Leben gehalten - es wird ja erst gelöscht wenn es keine shared_ptr mehr darauf gibt.

    Bzw. wenn du die Lebenszeit des Ziels unabhängig vom Verfolger kontrollieren willst, dann kannst du dem Verfolger auch einfach einen weak_ptr<Ziel> geben.

    Oder, wenn die Partei die die Lebenszeit des Ziels kontrolliert einen Level "überhalb" des Verfolgers angesiedelt ist, und das Verfolger Objekt auch "kennt", dann kannst du im Prinzip auch rohe Zeiger verwenden. Die Lebenszeit-kontrollierende Partei muss dann einfach nur verfolger->SetzeZiel(NULL) aufrufen bevor das Ziel gelöscht wird.
    Wobei das die am wenigsten "sichere" Variante ist. Weil Fehler die du dabei evtl. machst u.U. nicht oder erst spät auffallen.

    Wenn es sonst keine Punkte gibt die dagegensprechen (oder für eine andere Lösung) würde ich hier shared_ptr verwenden. Entweder, wenn das erwünscht ist, shared Ownership mit mehreren shared_ptr . Oder halt shared_ptr beim Owner und weak_ptr beim Verfolger.



  • Ich hab mir jetzt deinen Link zu single und shared ownership angesehen.

    Also wenn ich das richtig verstanden habe, wird bei einem unique_ptr der Speicher freigegeben, sobald er zerstört wird.
    Bei einem shared_ptr erst, wenn ALLE Referenzen zerstört werden.

    Also könnte ich doch mit mehreren Instanzen den gleichen Speicherbereich addressieren, sodass dieser erst freigegeben wird, sobald alle Instanzen zerstört werden, oder?

    Haltet ihr es generell sinvoll einen unique_ptr zu verwenden, anstatt eines standardmäßigen Zeigers? So muss man nicht daran denken, den Speicher selbst freizugeben und beugt so Speicherlücken(kann man so verlorenen Speicher bezeichnen?) vor.
    Einfacher ist es natürlich, Zeiger möglichst wenig zu verwenden und wenn dann vorsichtig mit ihnen umgehen, aber es steigert ja außerdem die Lesbarkeit und verhindert "Speichermüll".



  • coder++ schrieb:

    Haltet ihr es generell sinvoll einen unique_ptr zu verwenden, anstatt eines standardmäßigen Zeigers? So muss man nicht daran denken, den Speicher selbst freizugeben und beugt so Speicherlücken(kann man so verlorenen Speicher bezeichnen?) vor.

    In modernem C++ sind "besitzende" rohe Zeiger verpönt. Es ist also keine schlechte Idee unique_ptr zu verwenden.



  • MichelRT schrieb:

    coder++ schrieb:

    Haltet ihr es generell sinvoll einen unique_ptr zu verwenden, anstatt eines standardmäßigen Zeigers? So muss man nicht daran denken, den Speicher selbst freizugeben und beugt so Speicherlücken(kann man so verlorenen Speicher bezeichnen?) vor.

    In modernem C++ sind "besitzende" rohe Zeiger verpönt. Es ist also keine schlechte Idee unique_ptr zu verwenden.

    Okay, danke.
    Irgendwie hab ich das nie wirklich mitgekriegt, dass rohe Zeiger nicht mehr in modernes C++ gehören.



  • coder++ schrieb:

    Okay, danke.
    Irgendwie hab ich das nie wirklich mitgekriegt, dass rohe Zeiger nicht mehr in modernes C++ gehören.

    Doch schon, aber sie sollen eben nur zeigen und nichts anderes.
    Wenn man einen Besitz und damit die Pflicht zur Speicherfreigabe ausdruecken will, verwendet man soweit moeglich unique_ptr. Wenn es mehr als einen Besitzer gibt, sollte man erstmal sein Design ueberdenken und dann ggf. shared_ptr verwenden. shared_ptr schuetzt auch nicht in allen Situationen, z.B. bei zyklischen Abhaengigkeiten geht das nicht, die sollte man aber so gut wie nie haben.
    Nebenbei gilt noch, dass man fuer pointer auf Arrays ( int ptr = new int[10];* ) kein unique_ptr, sondern Container nehmen sollte.


Anmelden zum Antworten