Warum kann stackalloc nicht mit Referenztypen verwendet werden?

8

Wenn stackalloc mit Referenztypen wie folgt verwendet wird

%Vor%

es ist ein Fehler

  

Kann die Adresse nicht übernehmen, die Größe von erhalten oder einen Zeiger auf a deklarieren   verwalteter Typ ('String')

Warum ist das so? Warum kann CLR den Zeiger nicht auf einen verwalteten Typ deklarieren?

    
Konstantin Zadiran 24.02.2016, 09:25
quelle

4 Antworten

7

Das "Problem" ist größer: In C # können Sie keinen Zeiger auf einen verwalteten Typ haben. Wenn Sie versuchen, (in C #) zu schreiben:

%Vor%

Sie erhalten:

  

Kann die Adresse nicht übernehmen, die Größe von erhalten oder einen Zeiger auf einen verwalteten Typ ('string') deklarieren

Nun gibt stackalloc T[num] eine T* zurück (siehe zum Beispiel hier ), also klar stackalloc kann nicht mit Referenztypen verwendet werden.

Der Grund, warum Sie keinen Zeiger auf einen Referenztyp haben können, hängt wahrscheinlich damit zusammen, dass der GC Referenztypen frei um den Speicher bewegen kann (um den Speicher zu komprimieren), so dass die Gültigkeit eines Zeigers kurz sein kann.

Beachten Sie, dass es in C ++ / CLI möglich ist, einen Referenztyp festzulegen und seine Adresse zu übernehmen (siehe pin_ptr ) )

    
xanatos 24.02.2016, 09:29
quelle
2

Der Just-In-Time-Compiler in .NET führt zwei wichtige Aufgaben aus, wenn MSIL, wie vom C # -Compiler generiert, in ausführbaren Maschinencode konvertiert wird. Der offensichtliche und sichtbare generiert den Maschinencode. Der nicht offensichtliche und vollständig unsichtbare Job generiert eine Tabelle, die dem Garbage Collector mitteilt, wo nach Objektverweisen gesucht werden soll, wenn während der Ausführung der Methode ein GC auftritt.

Dies ist notwendig, weil Objekt-Roots nicht einfach im GC-Heap als Feld einer Klasse gespeichert werden können, sondern auch in lokalen Variablen oder CPU-Registern gespeichert werden. Um diesen Job richtig auszuführen, muss der Jitter die genaue Struktur des Stack-Frames und die Typen der dort gespeicherten Variablen kennen, damit er diese Tabelle korrekt erstellen kann. Damit später kann der Garbage Collector herausfinden, wie der richtige Stack-Frame-Offset oder das richtige CPU-Register gelesen werden muss, um den Objekt-Root-Wert zu erhalten. Ein Zeiger in den GC-Heap.

Das ist ein Problem, wenn Sie stackalloc verwenden. Diese Syntax nutzt eine CLR-Funktion, mit der ein Programm einen benutzerdefinierten Wertetyp deklarieren kann. Eine Hintertür zu normalen verwalteten Typdeklarationen mit der Einschränkung, dass dieser Wertetyp keine Felder enthalten darf. Nur ein Blob Speicher, es liegt am Programm, die richtigen Offsets in diesem Blob zu generieren. Der C # -Compiler hilft Ihnen, diese Offsets basierend auf der Typdeklaration und dem Indexausdruck zu generieren.

Sehr häufig in einem C ++ / CLI-Programm verwendet, kann dieselbe benutzerdefinierte Werttypeigenschaft den Speicher für ein systemeigenes C ++ - Objekt bereitstellen. Es ist nur Platz für das Speichern dieses Objekts erforderlich, wie man es richtig initialisiert und auf die Mitglieder dieses C ++ - Objekts zugreifen kann, ist ein Job, den der C ++ - Compiler herausfindet. Nichts, über das der GC Bescheid wissen sollte.

Die Kernrestriktion besteht also darin, dass es keine Möglichkeit gibt, Typinformationen für diesen Speicherblob bereitzustellen. Soweit es die CLR betrifft, sind dies nur einfache Bytes ohne Struktur, die Tabelle, die der GC verwendet, hat keine Option, seine interne Struktur zu beschreiben.

Zwangsläufig ist die einzige Art von Typ, die Sie verwenden können, die Art, die keinen Objektverweis erfordert, über den der GC informiert werden muss. Blitable Werttypen oder Zeiger. System.String ist also ein No-Go, es ist ein Referenztyp. Der nächste, den Sie möglicherweise bekommen könnten, ist "strähnig" ist:

%Vor%

Mit der weiteren Einschränkung, die Sie haben, können Sie sicherstellen, dass die char * -Elemente entweder auf eine gepinnte oder nicht verwaltete Zeichenkette zeigen. Und dass Sie das "Array" nicht außerhalb der Grenzen indexieren. Das ist nicht sehr praktisch.

    
Hans Passant 24.02.2016 10:37
quelle
0

Da C # im Gegensatz zu C ++ bei der Speicherbereinigung für die Speicher-Sicherheit arbeitet, wurde von Ihnen erwartet, dass Sie neunes Speichermanagement kennen.

zum Beispiel, werfen Sie einen Blick auf den nächsten Code:

%Vor%

Das Programm wird leicht abstürzen. Da arr dem Stack zugeordnet ist, verschwindet das Objekt + sein Speicher, sobald doAsync beendet ist. Die Lambda-Funktion zeigt immer noch auf diese nicht mehr gültige Speicheradresse, und dies ist ein ungültiger Zustand.

Wenn Sie lokale Primitive als Referenz übergeben, tritt das gleiche Problem auf.

Das Schema ist:
statische Objekte - & gt; lebt während der gesamten Anwendungszeit lokales Objekt - & gt; lebt, solange der Scope , der sie erstellt hat, gültig ist Heap-allokierte Objekte (erstellt mit new ) - & gt; existieren, solange jemand einen Verweis auf sie hält.

Ein weiteres Problem ist, dass die Garbage Collection in Perioden funktioniert. Wenn ein Objekt lokal ist, sollte es abgeschlossen werden, sobald die Funktion beendet ist, da nach dieser Zeit der Speicher von anderen Variablen überschrieben wird. Der GC kann nicht gezwungen werden, das Objekt zu finalisieren, oder sollte dies nicht tun.

Die gute Sache ist jedoch, dass der C # JIT manchmal (nicht immer) feststellen kann, dass ein Objekt sicher auf dem Stapel zugeordnet werden kann und greift auf die Stapelzuweisung zurück, wenn es möglich ist (wieder manchmal).

In C ++ hingegen können Sie alles in der Umgebung deklarieren, aber das ist weniger sicher als C # oder Java, aber Sie können Ihre Anwendung optimieren und eine hohe Leistung erreichen - Anwendung mit geringen Ressourcen

    
David Haim 24.02.2016 09:31
quelle
-1

Ich denke Xanatos hat die richtige Antwort gepostet.

Wie auch immer, das ist keine Antwort, sondern ein Gegenbeispiel zu einer anderen Antwort.

Betrachten Sie den folgenden Code:

%Vor%

Wenn Sie diesen Code ausführen, stürzt er ab, weil in das Stack-Array geschrieben wird, nachdem der Stack-Speicher für ihn freigegeben wurde.

Dies zeigt, dass der Grund, warum stackalloc nicht mit Referenztypen verwendet werden kann, nicht einfach diese Art von Fehler zu verhindern ist.

    
Matthew Watson 24.02.2016 09:53
quelle

Tags und Links