Votre tableau est alloué sur le tas et les entiers ne sont pas encadrés.
La source de votre confusion est probablement parce que les gens ont dit que les types de référence sont alloués sur le tas et que les types de valeur sont alloués sur la pile. Ce n'est pas une représentation entièrement exacte.
Toutes les variables et paramètres locaux sont alloués sur la pile. Cela inclut à la fois les types de valeur et les types de référence. La différence entre les deux est uniquement ce qui est stocké dans la variable. Sans surprise, pour un type valeur, la valeur du type est stockée directement dans la variable, et pour un type référence, la valeur du type est stockée sur le tas, et une référence à cette valeur est ce qui est stocké dans la variable.
Il en va de même pour les champs. Lorsque la mémoire est allouée pour une instance d'un type d'agrégat (a class
ou a struct
), elle doit inclure le stockage pour chacun de ses champs d'instance. Pour les champs de type référence, ce stockage contient simplement une référence à la valeur, qui serait elle-même allouée ultérieurement sur le tas. Pour les champs de type valeur, ce stockage contient la valeur réelle.
Donc, étant donné les types suivants:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
Les valeurs de chacun de ces types nécessiteraient 16 octets de mémoire (en supposant une taille de mot de 32 bits). Le champ I
dans chaque cas prend 4 octets pour stocker sa valeur, le champ S
prend 4 octets pour stocker sa référence et le champ L
prend 8 octets pour stocker sa valeur. Donc, la mémoire pour la valeur des deux RefType
et ValType
ressemble à ceci:
0 ┌───────────────────┐
│ je │
4 ├───────────────────┤
│ S │
8 ├───────────────────┤
│ L │
│ │
16 └───────────────────
Maintenant , si vous avez eu trois variables locales dans une fonction, des types RefType
, ValType
et int[]
, comme celui - ci:
RefType refType;
ValType valType;
int[] intArray;
alors votre pile pourrait ressembler à ceci:
0 ┌───────────────────┐
│ refType │
4 ├───────────────────┤
│ valType │
│ │
│ │
│ │
20 ├───────────────────┤
│ intArray │
24 └───────────────────
Si vous avez attribué des valeurs à ces variables locales, comme ceci:
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;
Ensuite, votre pile pourrait ressembler à ceci:
0 ┌───────────────────┐
│ 0x4A963B68 │ - adresse de tas de `refType`
4 ├───────────────────┤
│ 200 │ - valeur de `valType.I`
│ 0x4A984C10 │ - adresse de tas de `valType.S`
│ 0x44556677 │ - bas 32 bits de `valType.L`
│ 0x00112233 │ - 32 bits de haut de `valType.L`
20 ├───────────────────┤
│ 0x4AA4C288 │ - adresse de tas de `intArray`
24 └───────────────────
La mémoire à l'adresse 0x4A963B68
(valeur de refType
) serait quelque chose comme:
0 ┌───────────────────┐
│ 100 │ - valeur de `refType.I`
4 ├───────────────────┤
│ 0x4A984D88 │ - adresse de tas de `refType.S`
8 ├───────────────────┤
│ 0x89ABCDEF │ - 32 bits bas de `refType.L`
│ 0x01234567 │ - 32 bits de haut de `refType.L`
16 └───────────────────
La mémoire à l'adresse 0x4AA4C288
(valeur de intArray
) serait quelque chose comme:
0 ┌───────────────────┐
│ 4 │ - longueur du tableau
4 ├───────────────────┤
│ 300 │ - `intArray [0]`
8 ├───────────────────┤
│ 301 │ - `intArray [1]`
12 ├───────────────────
│ 302 │ - `intArray [2]`
16 ├───────────────────
│ 303 │ - `intArray [3]`
20 └───────────────────┘
Maintenant, si vous passiez intArray
à une autre fonction, la valeur poussée sur la pile serait 0x4AA4C288
, l'adresse du tableau, pas une copie du tableau.
Oui, le tableau sera situé sur le tas.
Les entiers à l'intérieur du tableau ne seront pas encadrés. Le simple fait qu'un type valeur existe sur le tas ne signifie pas nécessairement qu'il sera encadré. L'encadrement ne se produira que lorsqu'un type valeur, tel que int, est affecté à une référence de type objet.
Par exemple
Ne boxe pas:
Des boites:
Vous pouvez également consulter le post d'Eric sur ce sujet:
la source
Pour comprendre ce qui se passe, voici quelques faits:
Ainsi, si vous avez un tableau d'entiers, le tableau est alloué sur le tas et les entiers qu'il contient font partie de l'objet tableau sur le tas. Les entiers résident à l'intérieur de l'objet tableau sur le tas, pas en tant qu'objets séparés, ils ne sont donc pas encadrés.
Si vous avez un tableau de chaînes, c'est vraiment un tableau de références de chaînes. Les références étant des types valeur, elles feront partie de l'objet tableau sur le tas. Si vous placez un objet chaîne dans le tableau, vous placez en fait la référence à l'objet chaîne dans le tableau et la chaîne est un objet distinct sur le tas.
la source
Je pense qu'au cœur de votre question se trouve un malentendu sur les types de référence et de valeur. C'est probablement quelque chose avec lequel tous les développeurs .NET et Java ont eu du mal.
Un tableau n'est qu'une liste de valeurs. S'il s'agit d'un tableau d'un type référence (disons a
string[]
), alors le tableau est une liste de références à diversstring
objets sur le tas, car une référence est la valeur d'un type référence. En interne, ces références sont implémentées comme des pointeurs vers une adresse en mémoire. Si vous souhaitez visualiser ceci, un tel tableau ressemblerait à ceci en mémoire (sur le tas):[ 00000000, 00000000, 00000000, F8AB56AA ]
Il s'agit d'un tableau
string
contenant 4 références à desstring
objets sur le tas (les nombres ici sont hexadécimaux). Actuellement, seul le dernierstring
pointe vers quelque chose (la mémoire est initialisée à tous les zéros lorsqu'elle est allouée), ce tableau serait essentiellement le résultat de ce code en C #:Le tableau ci-dessus serait dans un programme 32 bits. Dans un programme 64 bits, les références seraient deux fois plus grandes (le
F8AB56AA
seraient00000000F8AB56AA
).Si vous avez un tableau de types valeur (disons an
int[]
), alors le tableau est une liste d'entiers, car la valeur d'un type valeur est la valeur elle-même (d'où le nom). La visualisation d'un tel tableau serait la suivante:[ 00000000, 45FF32BB, 00000000, 00000000 ]
Il s'agit d'un tableau de 4 entiers, où seul le deuxième entier reçoit une valeur (à 1174352571, qui est la représentation décimale de ce nombre hexadécimal) et le reste des entiers serait 0 (comme je l'ai dit, la mémoire est initialisée à zéro et 00000000 en hexadécimal est 0 en décimal). Le code qui a produit ce tableau serait:
Ce
int[]
tableau serait également stocké sur le tas.Comme autre exemple, la mémoire d'un
short[4]
tableau ressemblerait à ceci:[ 0000, 0000, 0000, 0000 ]
Comme la valeur de a
short
est un nombre de 2 octets.L'endroit où un type valeur est stocké n'est qu'un détail d'implémentation comme Eric Lippert l'explique très bien ici , non inhérent aux différences entre les types valeur et référence (qui est la différence de comportement).
Lorsque vous passez quelque chose à une méthode (qu'il s'agisse d'un type référence ou d'un type valeur), une copie de la valeur du type est en fait transmise à la méthode. Dans le cas d'un type référence, la valeur est une référence (pensez-y comme un pointeur vers un morceau de mémoire, bien que ce soit également un détail d'implémentation) et dans le cas d'un type valeur, la valeur est la chose elle-même.
L'encadrement ne se produit que si vous convertissez un type valeur en type référence. Ce code encadre:
la source
Ce sont des illustrations illustrant la réponse ci-dessus de @P Daddy
Et j'ai illustré le contenu correspondant dans mon style.
la source
Un tableau d'entiers est alloué sur le tas, ni plus, ni moins. myIntegers fait référence au début de la section où les entiers sont alloués. Cette référence se trouve sur la pile.
Si vous avez un tableau d'objets de type référence, comme le type Object, myObjects [], situé sur la pile, ferait référence au groupe de valeurs qui référencent les objets eux-mêmes.
Pour résumer, si vous passez myIntegers à certaines fonctions, vous ne transmettez la référence qu'à l'endroit où le vrai groupe d'entiers est alloué.
la source
Il n'y a pas de boxe dans votre exemple de code.
Les types de valeur peuvent vivre sur le tas comme ils le font dans votre tableau d'entiers. Le tableau est alloué sur le tas et il stocke des entiers, qui se trouvent être des types valeur. Le contenu du tableau est initialisé à la valeur par défaut (int), qui se trouve être zéro.
Considérez une classe qui contient un type valeur:
La variable h fait référence à une instance de HasAnInt qui vit sur le tas. Il se trouve juste qu'il contient un type valeur. C'est parfaitement normal, «i» se trouve juste à vivre sur le tas car il est contenu dans une classe. Il n'y a pas non plus de boxe dans cet exemple.
la source
Tout le monde en a assez dit, mais si quelqu'un cherche un exemple clair (mais non officiel) et une documentation sur le tas, la pile, les variables locales et les variables statiques, reportez-vous à l'article complet de Jon Skeet sur la mémoire en .NET - ce qui se passe où
Extrait:
Chaque variable locale (c'est-à-dire déclarée dans une méthode) est stockée sur la pile. Cela inclut les variables de type référence - la variable elle-même est sur la pile, mais rappelez-vous que la valeur d'une variable de type référence n'est qu'une référence (ou null), pas l'objet lui-même. Les paramètres de méthode comptent également comme des variables locales, mais s'ils sont déclarés avec le modificateur ref, ils n'obtiennent pas leur propre emplacement, mais partagent un emplacement avec la variable utilisée dans le code appelant. Voir mon article sur le passage de paramètres pour plus de détails.
Les variables d'instance pour un type de référence sont toujours sur le tas. C'est là que l'objet lui-même "vit".
Les variables d'instance pour un type valeur sont stockées dans le même contexte que la variable qui déclare le type valeur. L'emplacement de mémoire de l'instance contient effectivement les emplacements pour chaque champ de l'instance. Cela signifie (étant donné les deux points précédents) qu'une variable struct déclarée dans une méthode sera toujours sur la pile, alors qu'une variable struct qui est un champ d'instance d'une classe sera sur le tas.
Chaque variable statique est stockée sur le tas, qu'elle soit déclarée dans un type référence ou un type valeur. Il n'y a qu'un seul emplacement au total, quel que soit le nombre d'instances créées. (Il n'est cependant pas nécessaire de créer des instances pour que cet emplacement existe.) Les détails sur quel tas les variables vivent sont compliqués, mais expliqués en détail dans un article MSDN sur le sujet.
la source