Ich weiß, dass C # dem Programmierer die Möglichkeit gibt, auf Zeiger in einem unsicheren Kontext zuzugreifen und diese zu verwenden. Aber wann wird das gebraucht?
Unter welchen Umständen ist die Verwendung von Zeigern unvermeidlich?
Ist es nur aus Leistungsgründen?
Warum stellt C # diese Funktionalität in einem unsicheren Kontext zur Verfügung und entfernt alle verwalteten Vorteile daraus? Ist es möglich, Zeiger zu verwenden, ohne theoretisch die Vorteile einer verwalteten Umgebung zu verlieren?
Antworten:
Wenn die Nettokosten einer verwalteten, sicheren Lösung nicht akzeptabel sind, die Nettokosten einer unsicheren Lösung jedoch akzeptabel sind. Sie können die Nettokosten oder den Nettonutzen ermitteln, indem Sie den Gesamtnutzen von den Gesamtkosten abziehen. Die Vorteile einer unsicheren Lösung sind: "Keine Zeitverschwendung bei unnötigen Laufzeitprüfungen, um die Richtigkeit sicherzustellen." Die Kosten bestehen darin, (1) Code zu schreiben, der auch bei ausgeschaltetem verwalteten Sicherheitssystem sicher ist, und (2) den Garbage Collector möglicherweise weniger effizient zu machen, da er sich nicht im Speicher bewegen kann, in den ein nicht verwalteter Zeiger vorhanden ist es.
Oder wenn Sie die Person sind, die die Rangierschicht schreibt.
Es erscheint pervers, Zeiger in einer verwalteten Sprache aus anderen Gründen als der Leistung zu verwenden.
Sie können die Methoden in der Marshal-Klasse verwenden, um in den allermeisten Fällen mit nicht verwaltetem Code zusammenzuarbeiten. (Es kann einige Fälle geben, in denen es schwierig oder unmöglich ist, die Rangierausrüstung zur Lösung eines Interop-Problems zu verwenden, aber ich kenne keine.)
Natürlich, wie ich schon sagte, wenn Sie die Person das Schreiben der Marshal - Klasse sind dann bekommt man natürlich nicht zu verwenden , die Rangier - Schicht Ihr Problem zu lösen. In diesem Fall müssten Sie es mithilfe von Zeigern implementieren.
Diese verwalteten Vorteile sind mit Leistungskosten verbunden. Beispielsweise muss die Laufzeit jedes Mal, wenn Sie ein Array nach seinem zehnten Element fragen, eine Überprüfung durchführen, um festzustellen, ob ein zehntes Element vorhanden ist, und eine Ausnahme auslösen, wenn dies nicht der Fall ist. Mit Zeigern werden die Laufzeitkosten eliminiert.
Die entsprechenden Entwicklerkosten sind, dass Sie, wenn Sie es falsch machen, mit Speicherbeschädigungsfehlern umgehen müssen, die Ihre Festplatte formatieren und Ihren Prozess eine Stunde später zum Absturz bringen, anstatt sich zum Zeitpunkt des Fehlers mit einer netten, sauberen Ausnahme zu befassen.
Mit "Vorteilen" meine ich Vorteile wie Speicherbereinigung, Typensicherheit und referenzielle Integrität. Ihre Frage lautet also im Wesentlichen: "Ist es theoretisch möglich, das Sicherheitssystem auszuschalten und dennoch die Vorteile des eingeschalteten Sicherheitssystems zu nutzen?" Nein, klar ist es nicht. Wenn Sie dieses Sicherheitssystem ausschalten, weil Sie nicht mögen, wie teuer es ist, erhalten Sie nicht die Vorteile, wenn es eingeschaltet ist!
quelle
Zeiger sind ein inhärenter Widerspruch zur verwalteten, durch Müll gesammelten Umgebung.
Sobald Sie anfangen, mit rohen Zeigern herumzuspielen, hat der GC keine Ahnung, was los ist.
Insbesondere kann nicht festgestellt werden, ob Objekte erreichbar sind, da nicht bekannt ist, wo sich Ihre Zeiger befinden.
Es kann auch keine Objekte im Speicher verschieben, da dies Ihre Zeiger beschädigen würde.
All dies würde durch GC-verfolgte Zeiger gelöst; Das sind Referenzen.
Sie sollten Zeiger nur in chaotischen erweiterten Interop-Szenarien oder für hochentwickelte Optimierungen verwenden.
Wenn Sie fragen müssen, sollten Sie wahrscheinlich nicht.
quelle
interior_ptr
Der GC kann Referenzen verschieben. Die Verwendung von unsicher hält ein Objekt außerhalb der Kontrolle des GC und vermeidet dies. "Behoben" fixiert ein Objekt, lässt aber den GC den Speicher verwalten.
Wenn Sie per Definition einen Zeiger auf die Adresse eines Objekts haben und der GC ihn verschiebt, ist Ihr Zeiger nicht mehr gültig.
Warum Sie Zeiger benötigen: Der Hauptgrund ist die Arbeit mit nicht verwalteten DLLs, z. B. solchen, die in C ++ geschrieben wurden
Beachten Sie außerdem, dass Sie anfälliger für Heap-Fragmentierung sind, wenn Sie Variablen anheften und Zeiger verwenden.
Bearbeiten
Sie haben das Kernproblem von verwaltetem und nicht verwaltetem Code angesprochen ... Wie wird der Speicher freigegeben?
Sie können Code für die Leistung mischen, wie Sie es beschreiben. Sie können verwaltete / nicht verwaltete Grenzen einfach nicht mit Zeigern überschreiten (dh Sie können keine Zeiger außerhalb des "unsicheren" Kontexts verwenden).
Wie sie gereinigt werden ... Sie müssen Ihr eigenes Gedächtnis verwalten; Objekte, auf die Ihre Zeiger zeigen, wurden mithilfe von (hoffentlich) erstellt / zugewiesen (normalerweise innerhalb der C ++ - DLL)
CoTaskMemAlloc()
, und Sie müssen diesen Speicher auf dieselbe Weise freigeben, indemCoTaskMemFree()
Sie aufrufen , oder es kommt zu einem Speicherverlust. Beachten Sie, dass nur mit zugewiesener SpeicherCoTaskMemAlloc()
freigegeben werden kannCoTaskMemFree()
.Die andere Alternative besteht darin, eine Methode aus Ihrer nativen C ++ - DLL verfügbar zu machen, die einen Zeiger verwendet und freigibt. Auf diese Weise kann die DLL entscheiden, wie der Speicher freigegeben werden soll. Dies funktioniert am besten, wenn eine andere Methode zum Zuweisen von Speicher verwendet wird. Die meisten nativen DLLs, mit denen Sie arbeiten, sind DLLs von Drittanbietern, die Sie nicht ändern können, und sie haben normalerweise keine (wie ich gesehen habe) Funktionen zum Aufrufen.
Ein Beispiel für die Speicherfreigabe von hier :
string[] array = new string[2]; array[0] = "hello"; array[1] = "world"; IntPtr ptr = test(array); string result = Marshal.PtrToStringAuto(ptr); Marshal.FreeCoTaskMem(ptr); System.Console.WriteLine(result);
Noch etwas Lesematerial:
C # Freigabe des von IntPtr referenzierten Speichers In der zweiten Antwort werden die verschiedenen Zuordnungs- / Freigabemethoden erläutert
Wie kann ich IntPtr in C # freigeben? Verstärkt die Notwendigkeit, die Zuordnung auf dieselbe Weise aufzuheben, wie der Speicher zugewiesen wurde
http://msdn.microsoft.com/en-us/library/aa366533%28VS.85%29.aspx Offizielle MSDN-Dokumentation zu den verschiedenen Möglichkeiten zum Zuweisen und Freigeben von Speicher.
Kurz gesagt ... Sie müssen wissen, wie der Speicher zugewiesen wurde, um ihn freizugeben.
Bearbeiten Wenn ich Ihre Frage richtig verstehe, lautet die kurze Antwort Ja. Sie können die Daten an nicht verwaltete Zeiger übergeben, in einem unsicheren Kontext damit arbeiten und die Daten verfügbar haben, sobald Sie den unsicheren Kontext verlassen.
Der Schlüssel ist, dass Sie das verwaltete Objekt, auf das Sie verweisen, mit einem
fixed
Block verbinden müssen. Dies verhindert, dass der Speicher, auf den Sie verweisen, vom GC imunsafe
Block verschoben wird . Hier gibt es eine Reihe von Feinheiten, z. B. können Sie einen in einem festen Block initialisierten Zeiger nicht neu zuweisen. Sie sollten sich über unsichere und feste Anweisungen informieren, wenn Sie wirklich Ihren eigenen Code verwalten möchten.Die Vorteile der Verwaltung Ihrer eigenen Objekte und der Verwendung von Zeigern in der von Ihnen beschriebenen Weise führen jedoch möglicherweise nicht zu einer Leistungssteigerung, wie Sie vielleicht denken. Gründe warum nicht:
HTH,
James
quelle
GC.AddMemoryPressure
und explizit benachrichtigenGC.RemoveMemoryPressure
. Sie müssen den Speicher immer noch selbst freigeben, aber auf diese Weise berücksichtigt der Garbage Collector nicht verwalteten Speicher, wenn Sie Planungsentscheidungen treffen.Die häufigsten Gründe für die explizite Verwendung von Zeigern in C #:
Der Grund, warum die mit Zeigern verknüpfte Syntax aus C # entfernt wurde (nach meinem Wissen und Standpunkt - Jon Skeet würde besser mit B- antworten), war, dass sie sich in den meisten Situationen als überflüssig herausstellte.
Aus Sicht des Sprachdesigns müssen Sie, sobald Sie den Speicher durch einen Garbage Collector verwalten, strenge Einschränkungen einführen, was mit Zeigern möglich ist und was nicht. Die Verwendung eines Zeigers zum Zeigen in die Mitte eines Objekts kann beispielsweise schwerwiegende Probleme für den GC verursachen. Sobald die Einschränkungen vorhanden sind, können Sie die zusätzliche Syntax einfach weglassen und erhalten „automatische“ Referenzen.
Auch der ultra-wohlwollende Ansatz in C / C ++ ist eine häufige Fehlerquelle. In den meisten Situationen, in denen die Mikroleistung überhaupt keine Rolle spielt, ist es besser, strengere Regeln anzubieten und den Entwickler zugunsten weniger Fehler einzuschränken, die sehr schwer zu entdecken wären. Daher sind für gängige Geschäftsanwendungen die sogenannten "verwalteten" Umgebungen wie .NET und Java besser geeignet als Sprachen, die davon ausgehen, dass sie gegen die Bare-Metal-Maschine arbeiten.
quelle
obj->Property
,obj.Property
funktioniert stattdessen. Wird meine Antwort klarstellen.Angenommen, Sie möchten zwischen zwei Anwendungen über IPC (Shared Memory) kommunizieren, dann können Sie die Daten im Speicher zusammenfassen und diesen Datenzeiger über Windows Messaging oder ähnliches an die andere Anwendung übergeben. Beim Empfang der Bewerbung können Sie Daten zurückholen.
Nützlich auch beim Übertragen von Daten aus .NET in ältere VB6-Apps, bei denen Sie die Daten in den Speicher übertragen, den Zeiger mithilfe der Win-Meldung an die VB6-App übergeben und mit VB6 copymemory () Daten aus dem verwalteten Speicher in den nicht verwalteten Speicher von VB6-Apps abrufen Raum..
quelle