Types de tableaux, de tas et de pile et de valeur

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

Dans le code ci-dessus, new int[100] génère-t-il le tableau sur le tas? D'après ce que j'ai lu sur CLR via c#, la réponse est oui. Mais ce que je ne peux pas comprendre, c'est ce qui arrive aux int réels à l'intérieur du tableau. Comme ce sont des types de valeur, je suppose qu'ils devraient être encadrés, car je peux, par exemple, passer myIntegers à d'autres parties du programme et encombrer la pile s'ils étaient laissés dessus tout le temps. Ou je me trompe? Je suppose qu'ils seraient juste en boîte et vivraient sur le tas pour as longtemps le tableau existait.

120
demandé sur svick 2009-07-11 18:30:11

8 réponses

Votre tableau est alloué sur le tas, et les ints 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 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 seulement ce qui est stockées dans le variable. Sans surprise, pour un type de valeur de la valeur du type est directement stocké dans la variable, et d'un type de référence, la valeur de type est stocké sur le tas, et un 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 (une classe ou une structure), elle doit inclure le stockage pour chacun de ses champs d'instance. Pour les champs de type référence, ce stockage contient juste une référence à la valeur, qui serait elle-même allouée sur le tas plus tard. 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 est 8 octets pour stocker sa valeur. Donc la mémoire pour la valeur de RefType et ValType ressemble à ceci:

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

Maintenant, si vous aviez trois variables locales à une fonction, de types RefType, ValType, et int[], comme ceci:

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;

Alors votre pile pourrait ressembler à ceci:

 0 ┌───────────────────┐
   │    0x4A963B68     │ -- heap address of `refType`
 4 ├───────────────────┤
   │       200         │ -- value of `valType.I`
   │    0x4A984C10     │ -- heap address of `valType.S`
   │    0x44556677     │ -- low 32-bits of `valType.L`
   │    0x00112233     │ -- high 32-bits of `valType.L`
20 ├───────────────────┤
   │    0x4AA4C288     │ -- heap address of `intArray`
24 └───────────────────┘

Mémoire à l'adresse 0x4A963B68 (valeur de refType) serait quelque chose comme:

 0 ┌───────────────────┐
   │       100         │ -- value of `refType.I`
 4 ├───────────────────┤
   │    0x4A984D88     │ -- heap address of `refType.S`
 8 ├───────────────────┤
   │    0x89ABCDEF     │ -- low 32-bits of `refType.L`
   │    0x01234567     │ -- high 32-bits of `refType.L`
16 └───────────────────┘

Mémoire à adresse 0x4AA4C288 (valeur de intArray) serait quelque chose comme:

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

Maintenant, si vous avez passé intArray pour une autre fonction, la valeur poussé sur la pile serait 0x4AA4C288, l'adresse de la matrice pas une copie du tableau.

252
répondu P Daddy 2009-07-11 17:03:56

Oui, le tableau sera situé sur le tas.

Les ints à l'intérieur du tableau ne seront pas encadrés. Tout simplement parce qu'un type de valeur existe sur le tas, ne signifie pas nécessairement qu'il sera encadré. La boxe ne se produit que lorsqu'un type de valeur, tel que int, est affecté à une référence d'objet type.

Par exemple

Ne coche pas:

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

Boîtes:

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

Vous pouvez également consulter le post D'Eric à ce sujet sujet:

21
répondu JaredPar 2009-07-11 14:55:45

Pour comprendre ce qui se passe, voici quelques faits:

  • les objets sont toujours alloués sur le tas.
  • le tas ne contient que des objets.
  • les types de valeur sont soit alloués sur la pile, soit une partie d'un objet sur le tas.
  • un tableau est Un objet.
  • Un tableau ne peut contenir que des types de valeur.
  • une référence d'objet est un type de valeur.

Donc, si vous avez un tableau d'entiers, le tableau est alloué sur le tas et les entiers qu'il contient fait partie de l'objet tableau sur le tas. Les entiers résident dans l'objet tableau sur le tas, pas comme des objets séparés, donc ils ne sont pas encadrés.

Si vous avez un tableau de chaînes, c'est vraiment un tableau de références de chaînes. Comme les références sont des types de valeur, elles feront partie de l'objet tableau sur le tas. Si vous mettez un objet string dans le tableau, vous mettez réellement la référence à l'objet string dans le tableau, et la chaîne est un objet séparé sur le tas.

15
répondu Guffa 2009-07-11 15:49:06

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 lutté.

Un tableau est juste une liste de valeurs. Si c'est un tableau d'un type de référence (par exemple, une string[]), alors le tableau est une liste de références à divers string objets sur le tas, en tant que référence est le valeur d'un type de référence. En interne, ces références sont implémentées en tant que pointeurs vers une adresse en mémoire. Si vous souhaitez visualiser cela, un tel tableau ressemblerait à ceci en mémoire (sur le tas):

[ 00000000, 00000000, 00000000, F8AB56AA ]

C'est un tableau de {[5] } qui contient 4 références aux objets string sur le tas (les nombres ici sont hexadécimaux). Actuellement, seul le dernier string pointe réellement vers n'importe quoi (la mémoire est initialisée à tous les Zéro lorsqu'elle est allouée), ce tableau serait essentiellement le résultat de ce code en C#:

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

Le tableau ci-dessus serait dans un programme 32 bits. Dans un programme 64 bits, les références seraient deux fois plus grand (F8AB56AA est 00000000F8AB56AA).

Si vous avez un tableau de types de valeur (disons un int[]), alors le tableau est une liste de nombres entiers, comme le valeur d'une valeur de type est la valeur elle-même (d'où le nom). La visualisation d'un tel tableau serait la suivante:

[ 00000000, 45FF32BB, 00000000, 00000000 ]

C'est un tableau de 4 entiers, où seul le second int est assigné une valeur (à 1174352571, qui est la représentation décimale de cet hexadécimal nombre) et le reste des entiers de 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:

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

Ce tableau int[] serait également stocké sur le tas.

Comme autre exemple, la mémoire d'un tableau short[4] ressemblerait à ceci:

[ 0000, 0000, 0000, 0000 ]

Comme la valeur d'un short est un nombre de 2 octets.

Où un type de valeur est stocké, est juste une implémentation détail comme Eric Lippert l'explique très bien ici, pas inhérent aux différences entre les types de valeur et de référence (qui est la différence de comportement).

Lorsque vous passez quelque chose à une méthode (que le type d'une référence ou une valeur de type), puis copie de la valeur du type est réellement passé à la méthode. Dans le cas d'un type de référence, la valeur est une référence (pensez à ceci comme un pointeur vers un morceau de mémoire, bien que ce soit aussi un détail d'implémentation) et dans le cas d'un type de valeur, la valeur est la chose elle-même.

// 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){}

La boxe ne se produit que si vous convertissez un type de valeur en un type de référence. Ce code boîtes:

object o = 5;
8
répondu JulianR 2009-07-11 18:02:12

Un tableau d'entiers est alloué sur le tas, rien de plus, rien de moins. myintegers fait référence au début de la section où les ints sont alloués. Cette référence est située sur la pile.

Si vous avez un tableau d'objets de type de référence, comme le type D'objet, 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 passez que la référence au lieu où le vrai groupe d'entiers est alloué.

1
répondu Dykam 2009-07-11 14:38:02

Il n'y a pas de boxe dans votre exemple de code.

Types de valeurs peuvent vivre sur le tas comme ils le font dans votre tableau d'entiers. Le tableau est alloué sur le tas et stocke les ints, qui sont des types de valeur. Le contenu du tableau est initialisé à default (int), qui se trouve être zéro.

Considérons une classe qui contient un type de valeur:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

La Variable h fait référence à une instance de HasAnInt qui vit sur le tas. Il arrive juste de contenir un type de valeur. C'est parfaitement OK, 'je' arrive juste à vivre sur le tas comme il est contenu dans une classe. Il n'y a pas de boxe dans cet exemple non plus.

1
répondu Curt Nichols 2009-07-11 15:07:46

Assez a été dit par tout le monde, mais si quelqu'un cherche un exemple et une documentation clairs (mais non officiels) sur le tas, la pile, les variables locales et les variables statiques, reportez-vous à l'article complet de Jon Skeet sur mémoire dans. NET-what goes where

Extrait:

  1. 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 de référence - la variable elle-même est sur la pile, mais rappelez-vous que la valeur d'une variable de type de référence est seulement une référence (ou null), pas l'objet lui-même. Les paramètres de méthode comptent aussi comme variables locales, mais s'ils sont déclarés avec le modificateur ref, ils n'obtiennent pas leur propre slot, mais partagent un slot avec la variable utilisée dans le code appelant. Voir mon article sur le passage des paramètres pour plus de détails.

  2. 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".

  3. Exemple les variables de type valeur sont stockées dans le même contexte que la variable qui déclare le type de valeur. L'emplacement de mémoire pour l'instance contient effectivement les emplacements pour chaque champ dans l'instance. Cela signifie que (étant donné les deux points précédents) qu'une structure variable déclarée dans une méthode sera toujours sur la pile, alors qu'une structure variable qui est un champ d'instance d'une classe sera sur le tas.

  4. Chaque variable statique est stockée sur le tas, que ce soit il est déclaré dans un type de référence ou un type de valeur. Il n'y a qu'un seul emplacement au total, peu importe le nombre d'instances créées. (Il n'est pas nécessaire qu'il y ait des instances créées pour que cet emplacement existe.) Les détails du tas sur lequel les variables vivent sont compliqués, mais expliqués en détail dans un article MSDN sur le sujet.

1
répondu gmaran23 2013-04-09 08:37:50

Ce sont des illustrations représentant la réponse ci-dessus par @ P Daddy

entrez la description de l'image ici

entrez la description de l'image ici

Et j'ai illustré le contenu correspondant dans mon style.

entrez la description de l'image ici

0
répondu YoungMin Park 2017-12-07 07:32:03