Fragen zum Garbage Collector -> Stack, heap, ref , pointer (confused^^)



  • Speicherverwalter schrieb:

    - Der GC ruft immer dispose mit "false" auf ?

    IDisposeable.Dispose hat keine Parameter, und der GC ruft von sich aus auch nie IDisposeable.Dispose auf. Deshalb machen wir das ja im Finalizer selbst.

    - Kann man dispose manuell ohne das "using" dings da aufrufen?

    Ja.

    - Der Using Block bedeutet entspricht der Lebenesdauer des Objekts? (wie Stack)

    Muss nicht sein, ist aber meistens der Fall. Mit dem using-Block kann man sich jedenfalls ein bisschen RAII in C# machen 🙂



  • Hier noch ein Hinweis zum Dispose "Pattern":
    http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

    Simon



  • Ok danke jung, jetzt hab ich nur noch eine Frage hehe:
    Gib es sowas wie ein void pointer bzw. Referenz?

    nehm ich da einfach "object" als schlüssewort?



  • Gib es sowas wie ein void pointer bzw. Referenz?

    nehm ich da einfach "object" als schlüssewort?

    ja, einfach object.



  • Ich wäre trotzdem vorsichtig damit Referenzen mit Zeigern zu vergleichen, gerade wenn jemand von C++ kommt. Ein Zeiger ist ein direkter Zugriff auf die Startadresse eines Objekts im Speicher. Löscht man den Zeiger, löscht man auch das Objekt.

    Eine Referenz sagt gar nichts darüber aus wo im Speicher das Objekt ist. Vielmehr kann nur die Speicherverwaltung feststellen wo sich das Objekt gerade befindet. Erst in Verbindung mit der Speicherverwaltung kann man also von einer Referenz auf das Objekt auflösen.

    An der Stelle muß man sich auch noch etwas wichtiges klarmachen: Anders als in C++ kann ein Objekt im Managed Heap bei jedem GC Durchlauf seine Position im Speicher verändern.

    Ein auch entscheidender Unterschied:

    Wenn man 3 Zeiger auf ein Objekt hat und auf einen der Zeiger delete aufruft, wird das Objekt gelöscht und die anderen beiden Zeiger sind invalid.

    Wenn man dagegen 3 Referenzen auf ein Objekt hat und eine der Referenzen wird gelöscht (z.B. am Ende des using blocks) dann wird das Objekt NICHT gelöscht, sondern lebt solange weiter bis auch die letzte Referenz darauf gelöscht wird.

    imho: Referenzen mit Zeigern zu vergleichen wenn man als C++ Progger C# lernen will ist "an accident waiting to happen". Es ist wichtiger zu verstehen, das eine Referenz _kein_ Zeiger ist, sondern etwas anderes.



  • Löscht man den Zeiger, löscht man auch das Objekt.

    Zeiger werden nur dann gelöscht wenn Sie auf dem Heap erzeugt worden sind, was selten gemacht wird. Objekte, mit new erzeugt, werden mit delete über den Pointer gelöscht... der Pointer selbst wird dabei nicht gelöscht.

    Mit dem nötigen Abstand, denke ich, ist der Vergleich C++ Pointer und C# Referenz nicht verkehrt.

    Wenn man 3 Zeiger auf ein Objekt hat und auf einen der Zeiger delete aufruft, wird das Objekt gelöscht und die anderen beiden Zeiger sind invalid.

    Wenn man dagegen 3 Referenzen auf ein Objekt hat und eine der Referenzen wird gelöscht (z.B. am Ende des using blocks) dann wird das Objekt NICHT gelöscht, sondern lebt solange weiter bis auch die letzte Referenz darauf gelöscht wird.

    Nunja, dann stelle Dir die C# Referenz als Pointer mit Ref. Count vor...

    Simon



  • Referenzen mit Zeigern zu vergleichen ist schon OK. Ist vom Prinzip her dasselbe. Unterscheiden tut sich die Art der Speicherverwaltung, was aber nix mit dem Unterschied Referenz vs. Zeiger zu tun hat. Man kann beide Speicherverwaltungs-Arten (manuell, GC) frei mit beiden "Verweis-Arten" (Zeiger, Referenz) kombinieren. Oder nochmal konkret: es gibt auch Systeme wo ein GC mit echten Zeigern eingesetzt wird.

    Der gröbste Unterschied ist dass, wie schon erwähnt wurde, ein Zeiger auf eine feste Adresse zeigt, und eine Referenz ein etwas abstrakterer Verweis auf ein Objekt ist, wobei eben nicht garantiert ist dass sich die eigentliche Adresse des Objektes nicht ändert. Wichtig wird der Unterschied aber auch nur sobald man die Adresse eines Objektes irgendwo direkt verwendet, z.B. wenn man sie per PInvoke irgendeiner Funktion übergibt.



  • mal eine kurze Frage hierzu: in dem Dispose"Pattern" von MSDN wird ein Interopaufruf (closehandle) verwendet. Ist das wirklich notwendig ? Ich setze beim Cleanup in unsafe-Blöcken einfach IntPtr auf zero (wie es ja danach noch gemacht wird). Trotz teilweise großer Speichermengen (mehrere 100MB) und langen Laufzeiten (Tage) hatte ich kein Speicherleck. Ich weiß dass das nicht unbedingt beweisend ist aber dennoch: warum ? 😉



  • Das Beispiel in der MSDN ruft vermutlich deshalb closehandle auf, weil es irgendwo ein Handle anfordert. Du musst alle Ressourcen aufräumen, die nicht von .NET stammen. Welche das sind, können wir kaum wissen, deshalb: "Cannot predict."



  • HappyIntPtr schrieb:

    mal eine kurze Frage hierzu: in dem Dispose"Pattern" von MSDN wird ein Interopaufruf (closehandle) verwendet. Ist das wirklich notwendig ? Ich setze beim Cleanup in unsafe-Blöcken einfach IntPtr auf zero (wie es ja danach noch gemacht wird). Trotz teilweise großer Speichermengen (mehrere 100MB) und langen Laufzeiten (Tage) hatte ich kein Speicherleck. Ich weiß dass das nicht unbedingt beweisend ist aber dennoch: warum ? 😉

    Naja das kommt darauf an was in den IntPtr drinnensteht.

    Wenn es ein Zeiger auf einen Speicherbereich ist der über die GC Funktionen "gepinnt" wurde, dann reicht es AFAIK nicht aus einfach den IntPtr auf IntPtr.Zero zu setzen - der Block würde sonst nie vom GC collected (weil dieser denkt dass ein Programmteil über den er keine Informationen hat den Speicher noch verwendet - sonst könnte es ja passieren dass der GC Speicher "einsammelt" der noch von irgendwelchen unmanaged DLLs verwendet wird). In dem Fall müsstest du den Speicher auch über die entsprechende GC Funktion wieder ent-pinnen - das Nullsetzen des IntPtr ist dann aber eine Fleissaufgabe.

    Wenn der Speicher über das "fixed" Keyword gepinnt wurde dann musst du garnix machen, da der Block ja beim Verlassen des "fixed" Blocks automatisch "ent-pinnt" wird. Ein Nullsetzen des IntPtr ist dann aber auch nicht erforderlich.

    Und wenn du irgendwie die Adresse eines nicht-gepinnten Speicherbereichs in einen IntPtr bekommst, dann ist das sowieso ein Fehler, da dann nicht garantiert ist dass die Adresse sich nicht ändert (und ein IntPtr eben keine Referenz ist die der GC entsprechend anpassen würde wenn er etwas verschiebt, sondern eine einfache Zahl, die der GC nie ändern wird). Ich wüsste jetzt zwar garnicht wie man das ohne gröbere Hacks hinbekommt, aber falls es geht ist es auf jeden Fall Unsinn. Dass es mit grösseren Speicherbereichen oft trotzdem geht (weil diese in einem Eigenen Bereich allokiert und nicht vom GC verschoben werden) ist wieder so ne Sache -- verlassen sollte man sich halt nicht darauf.


Anmelden zum Antworten