Twoja tablica jest alokowana na stercie, a wartości int nie są opakowane.
Przyczyną twojego zamieszania jest prawdopodobnie to, że ludzie powiedzieli, że typy odwołań są przydzielane na stercie, a typy wartości są przydzielane na stosie. To nie jest całkowicie dokładne przedstawienie.
Wszystkie lokalne zmienne i parametry są przydzielane na stosie. Obejmuje to zarówno typy wartości, jak i typy odwołań. Różnica między nimi polega tylko na tym, co jest przechowywane w zmiennej. Nic dziwnego, że w przypadku typu wartości wartość typu jest przechowywana bezpośrednio w zmiennej, a dla typu referencyjnego wartość typu jest przechowywana na stercie, a odwołanie do tej wartości jest przechowywane w zmiennej.
To samo dotyczy pól. Gdy pamięć jest przydzielana dla instancji typu zagregowanego (a class
lub a struct
), musi ona obejmować pamięć dla każdego z jej pól instancji. W przypadku pól typu referencyjnego ta pamięć przechowuje tylko odniesienie do wartości, która zostanie później przydzielona na stercie. W przypadku pól typu wartość ta pamięć przechowuje rzeczywistą wartość.
Tak więc, biorąc pod uwagę następujące typy:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
Wartości każdego z tych typów wymagałyby 16 bajtów pamięci (przy założeniu 32-bitowego rozmiaru słowa). W I
każdym przypadku pole zajmuje 4 bajty na przechowywanie swojej wartości, pole S
zajmuje 4 bajty na przechowywanie odniesienia, a pole L
zajmuje 8 bajtów na przechowywanie wartości. Więc pamięć dla wartości obu RefType
i ValType
wygląda tak:
0 ┌───────────────────┐
│ I │
4 ├───────────────────┤
│ S │
8 ├───────────────────┤
│ L │
│ │
16 └───────────────────┘
Teraz, jeśli miał trzy zmienne lokalne w funkcji, typów RefType
, ValType
oraz int[]
, jak to:
RefType refType;
ValType valType;
int[] intArray;
Twój stos może wyglądać następująco:
0 ┌───────────────────┐
│ refType │
4 ├───────────────────┤
│ valType │
│ │
│ │
│ │
20 ├───────────────────┤
│ intArray │
24 └───────────────────┘
Jeśli przypisałeś wartości do tych zmiennych lokalnych, na przykład:
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;
Wtedy twój stos może wyglądać mniej więcej tak:
0 ┌───────────────────┐
│ 0x4A963B68 │ - adres sterty „refType”
4 ├───────────────────┤
│ 200 │ - wartość „valType.I”
│ 0x4A984C10 │ - adres sterty „valType.S”
│ 0x44556677 │ - niskie 32 bity wartości „valType.L”
│ 0x00112233 │ - wysokie 32 bity wartości „valType.L”
20 ├───────────────────┤
│ 0x4AA4C288 │ - adres sterty „intArray”
24 └───────────────────┘
Pamięć pod adresem 0x4A963B68
(wartość refType
) wyglądałaby tak:
0 ┌───────────────────┐
│ 100 │ - wartość „refType.I”
4 ├───────────────────┤
│ 0x4A984D88 │ - adres sterty „refType.S”
8 ├───────────────────┤
│ 0x89ABCDEF │ - niskie 32 bity wartości „refType.L”
│ 0x01234567 │ - wysokie 32 bity wartości „refType.L”
16 └───────────────────┘
Pamięć pod adresem 0x4AA4C288
(wartość intArray
) wyglądałaby tak:
0 ┌───────────────────┐
│ 4 │ - długość tablicy
4 ├───────────────────┤
│ 300 │ - `intArray [0]`
8 ├───────────────────┤
│ 301 │ - `intArray [1]`
12 ├───────────────────┤
│ 302 │ - `intArray [2]`
16 ├───────────────────┤
│ 303 │ - `intArray [3]`
20 └───────────────────┘
Teraz, jeśli przekazałeś intArray
do innej funkcji, wartość umieszczona na stosie byłaby 0x4AA4C288
adresem tablicy, a nie kopią tablicy.
Tak, tablica zostanie umieszczona na stercie.
Wartości int wewnątrz tablicy nie będą otoczone ramką. Tylko dlatego, że typ wartości istnieje na stercie, niekoniecznie oznacza, że zostanie on zapakowany. Boksowanie występuje tylko wtedy, gdy typ wartości, taki jak int, jest przypisany do odwołania do obiektu typu.
Na przykład
Nie box:
Pudła:
Możesz również sprawdzić post Erica na ten temat:
źródło
Aby zrozumieć, co się dzieje, oto kilka faktów:
Tak więc, jeśli masz tablicę liczb całkowitych, tablica jest alokowana na stercie, a liczby całkowite, które zawiera, są częścią obiektu tablicy na stercie. Liczby całkowite znajdują się wewnątrz obiektu tablicy na stercie, a nie jako oddzielne obiekty, więc nie są opakowane.
Jeśli masz tablicę ciągów, jest to w rzeczywistości tablica odniesień do ciągów. Ponieważ odwołania są typami wartości, będą one częścią obiektu tablicy na stercie. Jeśli umieścisz obiekt typu string w tablicy, w rzeczywistości umieścisz odniesienie do obiektu ciągu w tablicy, a łańcuch jest oddzielnym obiektem na stercie.
źródło
Myślę, że u podstaw twojego pytania leży niezrozumienie dotyczące typów referencyjnych i wartości. To jest coś, z czym prawdopodobnie borykał się każdy programista .NET i Java.
Tablica to po prostu lista wartości. Jeśli jest to tablica typu referencyjnego (powiedzmy a
string[]
), to tablica jest listą odniesień do różnychstring
obiektów na stercie, ponieważ referencja jest wartością typu referencyjnego. Wewnętrznie te odwołania są implementowane jako wskaźniki do adresu w pamięci. Jeśli chcesz to zwizualizować, taka tablica wyglądałaby tak w pamięci (na stercie):[ 00000000, 00000000, 00000000, F8AB56AA ]
To jest tablica
string
zawierająca 4 odniesienia dostring
obiektów na stercie (liczby tutaj są szesnastkowe). Obecnie tylko ostatniastring
faktycznie wskazuje na cokolwiek (pamięć jest inicjowana do wszystkich zer po przydzieleniu), ta tablica byłaby w zasadzie wynikiem tego kodu w C #:Powyższa tablica byłaby w programie 32-bitowym. W programie 64-bitowym odwołania byłyby dwa razy większe (
F8AB56AA
byłyby00000000F8AB56AA
).Jeśli masz tablicę typów wartości (powiedzmy
int[]
), to tablica jest lista liczb całkowitych, gdy wartość typu wartości jest sama wartość (stąd nazwa). Wizualizacja takiej tablicy wyglądałaby następująco:[ 00000000, 45FF32BB, 00000000, 00000000 ]
Jest to tablica 4 liczb całkowitych, w której tylko drugiej int ma przypisaną wartość (do 1174352571, która jest dziesiętną reprezentacją tej liczby szesnastkowej), a reszta liczb całkowitych będzie równa 0 (jak powiedziałem, pamięć jest inicjalizowana do zera a 00000000 szesnastkowo to 0 dziesiętnie). Kod, który utworzył tę tablicę, wyglądałby tak:
Ta
int[]
tablica byłaby również przechowywana na stercie.Jako inny przykład, pamięć
short[4]
tablicy wyglądałaby następująco:[ 0000, 0000, 0000, 0000 ]
Ponieważ wartość a
short
jest liczbą 2-bajtową.Gdzie przechowywany jest typ wartości, to tylko szczegół realizacja jak wyjaśnia Eric Lippert bardzo dobrze tutaj , nie wpisują się do różnic między wartością i typów referencyjnych (co jest różnica w zachowaniu).
Kiedy coś przekazać do metody (być, że typ odniesienia lub typ wartości) następnie kopia z wartości typu jest rzeczywiście przekazany do metody. W przypadku typu referencyjnego wartość jest referencją (pomyśl o tym jako wskaźniku do fragmentu pamięci, chociaż jest to również szczegół implementacyjny), aw przypadku typu wartości, wartość jest samą rzeczą.
Boksowanie występuje tylko wtedy, gdy konwertujesz typ wartości na typ referencyjny. Te skrzynki kodowe:
źródło
To są ilustracje przedstawiające powyższą odpowiedź autorstwa @P Daddy
Odpowiednie treści zilustrowałem w swoim stylu.
źródło
Tablica liczb całkowitych jest przydzielana na stercie, nic więcej, nic mniej. myIntegers odwołuje się do początku sekcji, w której są przydzielone wartości int. To odniesienie znajduje się na stosie.
Jeśli masz tablicę obiektów typu referencyjnego, takich jak typ Object, myObjects [], znajdujące się na stosie, odwoływałyby się do zbioru wartości, które odwołują się do samych obiektów.
Podsumowując, jeśli przekazujesz myIntegers do niektórych funkcji, przekazujesz tylko referencję do miejsca, w którym przydzielona jest prawdziwa paczka liczb całkowitych.
źródło
W Twoim przykładowym kodzie nie ma boksu.
Typy wartości mogą żyć na stercie, tak jak robią to w twojej tablicy int. Tablica jest alokowana na stercie i przechowuje wartości typu int, które są typami wartości. Zawartość tablicy jest inicjalizowana na wartość domyślną (int), która jest równa zero.
Rozważ klasę, która zawiera typ wartości:
Zmienna h odnosi się do instancji HasAnInt, która żyje na stercie. Tak się składa, że zawiera typ wartości. To zupełnie w porządku, tak się składa, że „ja” po prostu żyje na stercie, ponieważ jest zawarte w klasie. W tym przykładzie nie ma też boksu.
źródło
Wszyscy powiedzieli wystarczająco dużo, ale jeśli ktoś szuka jasnej (ale nieoficjalnej) próbki i dokumentacji dotyczącej sterty, stosu, zmiennych lokalnych i zmiennych statycznych, zapoznaj się z pełnym artykułem Jona Skeeta na temat pamięci w .NET - co się dzieje gdzie
Fragment:
Każda zmienna lokalna (tj. Jedna zadeklarowana w metodzie) jest przechowywana na stosie. Obejmuje to zmienne typu referencyjnego - sama zmienna znajduje się na stosie, ale pamiętaj, że wartość zmiennej typu referencyjnego jest tylko referencją (lub wartością zerową), a nie samym obiektem. Parametry metod są również liczone jako zmienne lokalne, ale jeśli są zadeklarowane z modyfikatorem ref, nie otrzymują własnego gniazda, ale współdzielą gniazdo ze zmienną używaną w kodzie wywołującym. Więcej informacji znajdziesz w moim artykule na temat przekazywania parametrów.
Zmienne instancji dla typu referencyjnego są zawsze na stercie. Tam „żyje” sam przedmiot.
Zmienne instancji dla typu wartości są przechowywane w tym samym kontekście, co zmienna, która deklaruje typ wartości. Gniazdo pamięci dla instancji faktycznie zawiera szczeliny dla każdego pola w instancji. Oznacza to (biorąc pod uwagę poprzednie dwa punkty), że zmienna struct zadeklarowana w metodzie zawsze będzie na stosie, podczas gdy zmienna struct, która jest polem instancji klasy, będzie na stercie.
Każda zmienna statyczna jest przechowywana na stercie, niezależnie od tego, czy została zadeklarowana w ramach typu referencyjnego, czy typu wartości. Bez względu na to, ile instancji utworzono, w sumie jest tylko jedno miejsce. (Nie trzeba jednak tworzyć żadnych instancji dla tego jednego miejsca). Szczegóły dotyczące dokładnie stosu, na którym znajdują się zmienne, są skomplikowane, ale wyjaśniono je szczegółowo w artykule MSDN na ten temat.
źródło