Ihr Array wird auf dem Heap zugewiesen, und die Ints werden nicht eingerahmt.
Die Ursache Ihrer Verwirrung ist wahrscheinlich, dass Leute gesagt haben, dass Referenztypen auf dem Heap und Werttypen auf dem Stapel zugewiesen werden. Dies ist keine ganz genaue Darstellung.
Alle lokalen Variablen und Parameter werden auf dem Stapel zugeordnet. Dies umfasst sowohl Werttypen als auch Referenztypen. Der Unterschied zwischen den beiden ist nur das, was in der Variablen gespeichert ist. Es ist nicht überraschend, dass für einen Werttyp der Wert des Typs direkt in der Variablen gespeichert wird, und für einen Referenztyp wird der Wert des Typs auf dem Heap gespeichert, und ein Verweis auf diesen Wert wird in der Variablen gespeichert.
Gleiches gilt für Felder. Wenn Speicher für eine Instanz eines Aggregattyps (a class
oder a struct
) zugewiesen wird , muss er Speicher für jedes seiner Instanzfelder enthalten. Bei Feldern vom Referenztyp enthält dieser Speicher nur einen Verweis auf den Wert, der später selbst auf dem Heap zugewiesen wird. Bei Feldern vom Werttyp enthält dieser Speicher den tatsächlichen Wert.
Also, angesichts der folgenden Typen:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
Die Werte für jeden dieser Typen würden 16 Byte Speicher erfordern (unter der Annahme einer Wortgröße von 32 Bit). Das Feld benötigt I
jeweils 4 Bytes, um seinen Wert zu speichern, das Feld S
benötigt 4 Bytes, um seine Referenz zu speichern, und das Feld L
benötigt 8 Bytes, um seinen Wert zu speichern. Also der Speicher für den Wert von beiden RefType
und ValType
sieht so aus:
0 ┌────────────────────┐
│ ich │
4 ├────────────────────┤
│ S │
8 ├────────────────────┤
│ L │
│ │
16 └────────────────────┘
Nun , wenn Sie hatte drei lokale Variablen in einer Funktion, von Typen RefType
, ValType
und int[]
, wie folgt aus :
RefType refType;
ValType valType;
int[] intArray;
dann könnte Ihr Stapel folgendermaßen aussehen:
0 ┌────────────────────┐
│ refType │
4 ├────────────────────┤
│ valType │
│ │
│ │
│ │
20 ├────────────────────┤
│ intArray │
24 └────────────────────┘
Wenn Sie diesen lokalen Variablen Werte zugewiesen haben, wie folgt:
refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;
valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;
intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;
Dann könnte Ihr Stapel ungefähr so aussehen:
0 ┌────────────────────┐
│ 0x4A963B68 │ - Heap-Adresse von `refType`
4 ├────────────────────┤
│ 200 │ - Wert von `valType.I`
│ 0x4A984C10 │ - Heap-Adresse von `valType.S`
│ 0x44556677 │ - niedrige 32-Bit von `valType.L`
│ 0x00112233 │ - hohe 32-Bit von `valType.L`
20 ├────────────────────┤
│ 0x4AA4C288 │ - Heap-Adresse von `intArray`
24 └────────────────────┘
Speicher an Adresse 0x4A963B68
(Wert von refType
) wäre so etwas wie:
0 ┌────────────────────┐
│ 100 │ - Wert von `refType.I`
4 ├────────────────────┤
│ 0x4A984D88 │ - Heap-Adresse von `refType.S`
8 ├────────────────────┤
│ 0x89ABCDEF │ - niedrige 32-Bit von `refType.L`
│ 0x01234567 │ - hohe 32-Bit von `refType.L`
16 └────────────────────┘
Speicher an Adresse 0x4AA4C288
(Wert von intArray
) wäre so etwas wie:
0 ┌────────────────────┐
│ 4 │ - Länge des Arrays
4 ├────────────────────┤
│ 300 │ - `intArray [0]`
8 ├────────────────────┤
│ 301 │ - `intArray [1]`
12 ├────────────────────┤
│ 302 │ - `intArray [2]`
16 ├────────────────────┤
│ 303 │ - `intArray [3]`
20 └────────────────────┘
Wenn Sie nun an intArray
eine andere Funktion übergeben, lautet der auf den Stapel übertragene Wert 0x4AA4C288
die Adresse des Arrays und keine Kopie des Arrays.
Ja, das Array befindet sich auf dem Heap.
Die Ints innerhalb des Arrays werden nicht eingerahmt. Nur weil ein Werttyp auf dem Heap vorhanden ist, bedeutet dies nicht unbedingt, dass er eingerahmt wird. Boxing tritt nur auf, wenn ein Werttyp wie int einer Referenz vom Typ Objekt zugewiesen ist.
Beispielsweise
Boxt nicht:
Boxen:
Vielleicht möchten Sie auch Erics Beitrag zu diesem Thema lesen:
quelle
Um zu verstehen, was passiert, sind hier einige Fakten:
Wenn Sie also ein Array von Ganzzahlen haben, wird das Array auf dem Heap zugewiesen und die darin enthaltenen Ganzzahlen sind Teil des Array-Objekts auf dem Heap. Die Ganzzahlen befinden sich innerhalb des Array-Objekts auf dem Heap und nicht als separate Objekte, sodass sie nicht in Kästchen eingeschlossen sind.
Wenn Sie ein Array von Zeichenfolgen haben, ist es wirklich ein Array von Zeichenfolgenreferenzen. Da Referenzen Werttypen sind, sind sie Teil des Array-Objekts auf dem Heap. Wenn Sie ein Zeichenfolgenobjekt in das Array einfügen, fügen Sie tatsächlich den Verweis auf das Zeichenfolgenobjekt in das Array ein, und die Zeichenfolge ist ein separates Objekt auf dem Heap.
quelle
Ich denke, im Kern Ihrer Frage liegt ein Missverständnis über Referenz- und Werttypen. Dies ist etwas, mit dem wahrscheinlich jeder .NET- und Java-Entwickler zu kämpfen hat.
Ein Array ist nur eine Liste von Werten. Wenn es sich um ein Array eines Referenztyps handelt (z. B. a
string[]
), ist das Array eine Liste von Referenzen auf verschiedenestring
Objekte auf dem Heap, da eine Referenz der Wert eines Referenztyps ist. Intern werden diese Referenzen als Zeiger auf eine Adresse im Speicher implementiert. Wenn Sie dies visualisieren möchten, würde ein solches Array im Speicher (auf dem Heap) folgendermaßen aussehen:[ 00000000, 00000000, 00000000, F8AB56AA ]
Dies ist ein Array
string
, das 4 Verweise aufstring
Objekte auf dem Heap enthält (die Zahlen hier sind hexadezimal). Derzeit zeigt nur der letztestring
tatsächlich auf etwas (der Speicher wird bei der Zuweisung auf alle Nullen initialisiert). Dieses Array wäre im Grunde das Ergebnis dieses Codes in C #:Das obige Array würde sich in einem 32-Bit-Programm befinden. In einem 64-Bit-Programm wären die Referenzen doppelt so groß (
F8AB56AA
wären00000000F8AB56AA
).Wenn Sie einen Array von Werttypen haben (sagt eine
int[]
) , dann das Array ist eine Liste von ganzen Zahlen, wie der Wert eines Werttypen ist der Wert selbst (daher der Name). Die Visualisierung eines solchen Arrays wäre folgende:[ 00000000, 45FF32BB, 00000000, 00000000 ]
Dies ist ein Array von 4 Ganzzahlen, wobei nur dem zweiten int ein Wert zugewiesen wird (bis 1174352571, was die Dezimaldarstellung dieser Hexadezimalzahl ist) und der Rest der Ganzzahlen 0 wäre (wie gesagt, der Speicher wird auf Null initialisiert und 00000000 in hexadezimal ist 0 in dezimal). Der Code, der dieses Array erzeugt hat, wäre:
Dieses
int[]
Array würde auch auf dem Heap gespeichert.Als weiteres Beispiel würde der Speicher eines
short[4]
Arrays folgendermaßen aussehen:[ 0000, 0000, 0000, 0000 ]
Da der Wert von a
short
eine 2-Byte-Zahl ist.Wo ein Werttyp gespeichert ist, ist nur ein Implementierungsdetail, wie Eric Lippert hier sehr gut erklärt , und nicht den Unterschieden zwischen Wert- und Referenztypen (was ein Unterschied im Verhalten ist) inhärent.
Wenn Sie etwas an eine Methode übergeben (sei es ein Referenztyp oder ein Werttyp), wird tatsächlich eine Kopie des Werts des Typs an die Methode übergeben. Im Fall eines Referenztyps ist der Wert eine Referenz (stellen Sie sich dies als Zeiger auf ein Stück Speicher vor, obwohl dies auch ein Implementierungsdetail ist), und im Fall eines Werttyps ist der Wert das Ding selbst.
Boxing tritt nur auf, wenn Sie einen Werttyp in einen Referenztyp konvertieren . Diese Codefelder:
quelle
Dies sind Abbildungen, die die obige Antwort von @P Daddy zeigen
Und ich habe die entsprechenden Inhalte in meinem Stil illustriert.
quelle
Auf dem Heap wird ein Array von Ganzzahlen zugewiesen, nicht mehr und nicht weniger. myIntegers verweist auf den Anfang des Abschnitts, in dem die Ints zugewiesen sind. Diese Referenz befindet sich auf dem Stapel.
Wenn Sie ein Array von Objekten vom Referenztyp haben, wie z. B. den Objekttyp, verweist myObjects [] auf dem Stapel auf die Werte, die auf die Objekte selbst verweisen.
Zusammenfassend lässt sich sagen, dass Sie, wenn Sie myIntegers an einige Funktionen übergeben, den Verweis nur an die Stelle übergeben, an der die tatsächliche Anzahl von Ganzzahlen zugewiesen ist.
quelle
Ihr Beispielcode enthält kein Boxen.
Werttypen können auf dem Heap wie in Ihrem Ints-Array gespeichert werden. Das Array wird auf dem Heap zugewiesen und speichert Ints, bei denen es sich zufällig um Werttypen handelt. Der Inhalt des Arrays wird auf den Standardwert (int) initialisiert, der zufällig Null ist.
Stellen Sie sich eine Klasse vor, die einen Werttyp enthält:
Die Variable h bezieht sich auf eine Instanz von HasAnInt, die auf dem Heap lebt. Es enthält nur einen Werttyp. Das ist vollkommen in Ordnung. Ich lebe zufällig auf dem Haufen, da er in einer Klasse enthalten ist. Auch in diesem Beispiel gibt es kein Boxen.
quelle
Alle haben genug gesagt, aber wenn jemand nach einem klaren (aber nicht offiziellen) Beispiel und einer Dokumentation zu Heap, Stack, lokalen Variablen und statischen Variablen sucht, lesen Sie den vollständigen Artikel von Jon Skeet über Memory in .NET - was geht wo
Auszug:
Jede lokale Variable (dh eine in einer Methode deklarierte) wird auf dem Stapel gespeichert. Dazu gehören Referenztypvariablen - die Variable selbst befindet sich auf dem Stapel. Beachten Sie jedoch, dass der Wert einer Referenztypvariablen nur eine Referenz (oder Null) ist, nicht das Objekt selbst. Methodenparameter gelten ebenfalls als lokale Variablen. Wenn sie jedoch mit dem Modifikator ref deklariert werden, erhalten sie keinen eigenen Slot, sondern teilen sich einen Slot mit der im aufrufenden Code verwendeten Variablen. Weitere Informationen finden Sie in meinem Artikel zur Parameterübergabe.
Instanzvariablen für einen Referenztyp befinden sich immer auf dem Heap. Dort "lebt" das Objekt selbst.
Instanzvariablen für einen Werttyp werden im selben Kontext gespeichert wie die Variable, die den Werttyp deklariert. Der Speichersteckplatz für die Instanz enthält effektiv die Steckplätze für jedes Feld innerhalb der Instanz. Dies bedeutet (unter Berücksichtigung der beiden vorherigen Punkte), dass sich eine innerhalb einer Methode deklarierte Strukturvariable immer auf dem Stapel befindet, während sich eine Strukturvariable, die ein Instanzfeld einer Klasse ist, auf dem Heap befindet.
Jede statische Variable wird auf dem Heap gespeichert, unabhängig davon, ob sie in einem Referenztyp oder einem Werttyp deklariert ist. Insgesamt gibt es nur einen Steckplatz, unabhängig davon, wie viele Instanzen erstellt wurden. (Es müssen jedoch keine Instanzen erstellt werden, damit dieser eine Slot vorhanden ist.) Die Details, auf welchem Heap die Variablen leben, sind kompliziert, werden jedoch in einem MSDN-Artikel zu diesem Thema ausführlich erläutert.
quelle