Vraag Arrays, heap en stack en waardetypes


int[] myIntegers;
myIntegers = new int[100];

Is in de bovenstaande code de nieuwe int [100] de array op de heap aan het genereren? Van wat ik heb gelezen over CLR via c #, is het antwoord ja. Maar wat ik niet kan begrijpen, is wat er gebeurt met de werkelijke int's in de array. Aangezien dit waardetypes zijn, denk ik dat ze moeten worden ingepakt, omdat ik bijvoorbeeld myIntegers kan doorgeven aan andere delen van het programma en het stapelgeheugen zou oplopen als ze er de hele tijd op zouden zitten . Of zie ik dat verkeerd? Ik vermoed dat ze gewoon in de doos zouden zitten en zouden leven op de hoop zo lang de reeks bestond.


119
2017-07-11 14:30


oorsprong


antwoorden:


Uw array is toegewezen op de heap en de ints zijn niet in een vak.

De bron van je verwarring is waarschijnlijk omdat mensen hebben gezegd dat referentietypen zijn toegewezen aan de heap en dat waardetypen zijn toegewezen aan de stapel. Dit is geen volledig nauwkeurige weergave.

Alle lokale variabelen en parameters worden toegewezen aan de stapel. Dit omvat zowel waardetypes als referentietypen. Het verschil tussen de twee is alleen wat is opgeslagen in de variabele. Niet verrassend, voor een waarde type, de waarde van het type wordt direct in de variabele opgeslagen en voor een referentietype wordt de waarde van het type op de heap opgeslagen en een referentie naar deze waarde is wat is opgeslagen in de variabele.

Hetzelfde geldt voor velden. Wanneer geheugen wordt toegewezen voor een instantie van een aggregatietype (een klasse of een struct), moet het opslag voor elk van de instantievelden bevatten. Voor velden van het referentietype bevat deze opslag alleen een verwijzing naar de waarde die later zelf aan de heap zou worden toegewezen. Voor velden van het waardetype bevat deze opslag de werkelijke waarde.

Dus, gezien de volgende types:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

Voor de waarden van elk van deze typen is 16 bytes aan geheugen vereist (uitgaande van een 32-bits woordgrootte). Het veld I in elk geval 4 bytes nodig om zijn waarde op te slaan, het veld S heeft 4 bytes nodig om zijn referentie en het veld op te slaan L heeft 8 bytes nodig om de waarde op te slaan. Dus het geheugen voor de waarde van beide RefType en ValType het lijkt hierop:

 0 ┌───────────────────┐
   │ Ik │
 4 ├───────────────────┤
   │ S │
 8 ├───────────────────┤
   │ L │
   │ │
16 └───────────────────┘

Als u nu drie lokale variabelen had in een functie, van typen RefType, ValType, en int[], soortgelijk:

RefType refType;
ValType valType;
int[]   intArray;

dan ziet je stapel er als volgt uit:

 0 ┌───────────────────┐
   │ refType │
 4 ├───────────────────┤
   │ valType │
   │ │
   │ │
   │ │
20 ├───────────────────┤
   │ intArray │
24 └───────────────────┘

Als u waarden aan deze lokale variabelen hebt toegewezen, zoals:

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;

Dan ziet je stapel er ongeveer zo uit:

 0 ┌───────────────────┐
   │ 0x4A963B68 │ - heapadres van `refType`
 4 ├───────────────────┤
   │ 200 │ - waarde van `valType.I`
   │ 0x4A984C10 │ - heapadres van `valType.S`
   │ 0x44556677 │ - lage 32-bits van `valType.L`
   │ 0x00112233 │ - hoge 32-bits van `valType.L`
20 ├───────────────────┤
   │ 0x4AA4C288 │ - heap adres van `intArray`
24 └───────────────────┘

Geheugen op adres 0x4A963B68 (waarde van refType) zou zoiets zijn als:

 0 ┌───────────────────┐
   │ 100 │ - waarde van `refType.I`
 4 ├───────────────────┤
   │ 0x4A984D88 │ - heapadres van `refType.S`
 8 ├───────────────────┤
   │ 0x89ABCDEF │ - lage 32-bits van `refType.L`
   │ 0x01234567 │ - hoge 32-bits van `refType.L`
16 └───────────────────┘

Geheugen op adres 0x4AA4C288 (waarde van intArray) zou zoiets zijn als:

 0 ┌───────────────────┐
   │ 4 │ - lengte van de array
 4 ├───────────────────┤
   │ 300 │ - `intArray [0]`
 8 ├───────────────────┤
   │ 301 │ - `intArray [1]`
12 ├───────────────────┤
   │ 302 │ - `intArray [2]`
16 ├───────────────────┤
   │ 303 │ - `intArray [3]`
20 └───────────────────┘

Nu als je geslaagd bent intArray naar een andere functie, zou de waarde die op de stapel wordt gedrukt 0x4AA4C288 zijn, het adres van de array, niet een kopie van de array.


249
2017-07-11 17:03



Ja, de array zal zich op de heap bevinden.

De ints in de array worden niet in een vak geplaatst. Alleen omdat er een waardetype op de heap bestaat, wil dit nog niet zeggen dat het in een vak wordt ingevoegd. Boksen komt alleen voor als een waardetype, zoals int, is toegewezen aan een verwijzing van het type object.

Bijvoorbeeld

Doet niet:

int i = 42;
myIntegers[0] = 42;

dozen:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

Misschien wil je ook het bericht van Eric over dit onderwerp bekijken:


21
2017-07-11 14:35



Om te begrijpen wat er gebeurt, volgen hier enkele feiten:

  • Object wordt altijd op de hoop toegewezen.
  • De heap bevat alleen objecten.
  • Waardetypes worden toegewezen aan de stapel of een deel van een object op de stapel.
  • Een array is een object.
  • Een array kan alleen waardetypes bevatten.
  • Een objectreferentie is een waardetype.

Dus als u een array van gehele getallen hebt, wordt de array toegewezen aan de heap en de gehele getallen die deze bevat, maken deel uit van het array-object op de heap. De gehele getallen bevinden zich in het array-object op de heap, niet als afzonderlijke objecten, dus ze zijn niet in een vak.

Als je een array van strings hebt, is het echt een array van stringreferenties. Omdat verwijzingen waardetypes zijn, maken ze deel uit van het array-object op de heap. Als u een stringobject in de array plaatst, plaatst u de verwijzing naar het stringobject in de array en is de tekenreeks een afzonderlijk object op de heap.


15
2017-07-11 15:49



Ik denk dat de kern van je vraag een misverstand is over referentie- en waardetypen. Dit is iets waar waarschijnlijk elke .NET- en Java-ontwikkelaar mee worstelde.

Een array is slechts een lijst met waarden. Als het een array van een referentietype is (bijvoorbeeld a string[]) dan is de array een lijst met verwijzingen naar verschillende string objecten op de hoop, als een verwijzing is de waarde van een referentietype. Intern worden deze verwijzingen geïmplementeerd als verwijzingen naar een adres in het geheugen. Als je dit wilt visualiseren, ziet zo'n array er in het geheugen (op de heap) als volgt uit:

[ 00000000, 00000000, 00000000, F8AB56AA ]

Dit is een reeks van string die 4 referenties bevat naar string objecten op de heap (de getallen hier zijn hexadecimaal). Momenteel alleen de laatste string wijst in feite naar iets (geheugen wordt geïnitialiseerd naar alle nullen bij toewijzing), deze array zou in feite het resultaat zijn van deze code in C #:

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

De bovenstaande array zou zich in een 32-bits programma bevinden. In een 64-bits programma zouden de referenties tweemaal zo groot zijn (F8AB56AA zou zijn 00000000F8AB56AA).

Als u een reeks waardetypen heeft (bijvoorbeeld een int[]) dan is de array een lijst van gehele getallen, zoals de waarde van een waardetype is de waarde zelf (vandaar de naam). De visualisatie van zo'n array zou dit zijn:

[ 00000000, 45FF32BB, 00000000, 00000000 ] 

Dit is een array van 4 gehele getallen, waarbij alleen de tweede int een waarde krijgt toegewezen (tot 1174352571, wat de decimale weergave van dat hexadecimale getal is) en de rest van de gehele getallen 0 zouden zijn (zoals ik al zei, het geheugen is geïnitialiseerd op nul en 00000000 in hexadecimaal is 0 in decimaal). De code die deze array produceerde zou zijn:

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

Deze int[] array zou ook op de heap worden opgeslagen.

Als een ander voorbeeld, de herinnering aan een short[4] array zou er als volgt uitzien:

[ 0000, 0000, 0000, 0000 ]

Als de waarde van a short is een 2-byte nummer.

Waar een waardetype is opgeslagen, is slechts een implementatiedetail zoals Eric Lippert het heel goed uitlegt hier, niet inherent aan de verschillen tussen waarde- en referentietypen (wat een verschil in gedrag is).

Wanneer u iets doorgeeft aan een methode (of dat nu een referentietype of een waardetype is), dan kopiëren van de waarde van het type wordt eigenlijk doorgegeven aan de methode. In het geval van een referentietype, de waarde is een referentie (zie dit als een verwijzing naar een stukje geheugen, hoewel dat ook een implementatiedetail is) en in het geval van een waardetype is de waarde het ding zelf.

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

Boksen komt alleen voor als jij converteren een waardetype naar een referentietype. Deze codevakjes:

object o = 5;

8
2017-07-11 16:57



Een array van gehele getallen wordt op de hoop toegewezen, niets meer en niets minder. myIntegers verwijst naar het begin van de sectie waarin de ints zijn toegewezen. Die referentie bevindt zich op de stapel.

Als u een array van referentietypeobjecten hebt, zoals het objecttype, verwijst myObjects [] in de stapel naar de reeks waarden die verwijzen naar de objecten zelf.

Om samen te vatten, als je myIntegers aan sommige functies doorgeeft, geef je alleen de verwijzing door naar de plaats waar de echte groep integers is toegewezen.


1
2017-07-11 14:38



Er is geen boksen in uw voorbeeldcode.

Waardetypes kunnen op de heap leven zoals ze in uw array van ints doen. De array wordt toegewezen aan de heap en deze slaat ints op, die waardetypes zijn. De inhoud van de array wordt geïnitialiseerd naar standaard (int), wat toevallig nul is.

Overweeg een klasse die een waardetype bevat:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

Variabele h verwijst naar een instantie van HasAnInt die op de hoop leeft. Het bevat toevallig een waardetype. Dat is perfect, 'ik' gebeurt toevallig op de hoop omdat het in een klas zit. Er is ook geen boksen in dit voorbeeld.


1
2017-07-11 15:07



Iedereen heeft genoeg gezegd, maar als iemand op zoek is naar een duidelijke (maar niet-officiële) steekproef en documentatie over heap, stack, lokale variabelen en statische variabelen, verwijs dan naar het volledige artikel van Jon Skeet op Geheugen in .NET - wat is waar

Uittreksel:


1
2018-04-09 07:43