Types de valeur et Types de référence

Le type d’un élément de données définit deux choses : la taille mémoire nécessaire pour le stocker (le nombre de bits) ainsi que les membres de données qu’il contient. Par ailleurs, le type détermine si un objet est placé en mémoire sur la Pile (Stack) ou sur le Tas (Heap).

Les types se répartissent en deux grandes catégories : Types de valeur (Value Types) et Types de référence (Reference Types). Ces deux familles d’objets utilisent des mécanismes de stockage mémoire totalement distincts.

1. Un type de valeur occupe un seul bloc de mémoire, dans lequel les données brutes sont stockées directement.

Les données des types de valeur résident directement sur la Pile Stack. La zone Data contient la valeur brute ; par exemple avec int age = 12 ; le nombre 12 est enregistré directement dans cette zone de la pile

2. Les types de référence nécessitent deux segments mémoire séparés :

  • Le premier segment conserve les données réelles de l’objet et cet espace est toujours alloué sur le Tas ;
  • Le second segment est une adresse de référence qui pointe vers l’emplacement des données sur le tas. (La pile stocke l’adresse du tas ; à l’époque du langage C, on appelait cela un pointeur)
Les données principales d’un type de référence sont stockées sur le tas, avec une adresse unique comme 000001, tandis que la pile conserve cette adresse de référence Reference

Les deux schémas ci-dessus ne valent que pour des objets autonomes qui ne sont pas des membres de données d’un autre objet.

Attention

La partie de données propre à tout objet de type référence est systématiquement placée sur le tas, comme le montrent les illustrations.

Cependant, les instances de type de valeur ou les adresses de référence des types de référence peuvent se trouver soit sur la pile, soit sur le tas, selon la structure du code.

Exemple :

Imaginons un type de référence Person comportant deux membres : un membre de type valeur et un membre de type référence.

Beaucoup de développeurs pensent à tort que les membres de type valeur sont sur la pile, les adresses de référence aussi sur la pile et le corps des objets sur le tas (comme sur les images), mais cette idée est erronée.

Retenez une règle essentielle : l’ensemble des données brutes d’une instance de type référence est définitivement alloué sur le tas. Les deux membres font partie des données de cet objet, donc qu’ils soient de type valeur ou de référence, ils sont stockés ensemble sur le tas.

Exemple de code :

class Program
{
    static void Main(string[] args)
    {
        int a = 10;
        Person bill = new Person("Bill", 18);
    }

}

class Person
{
    private string name;

    private int age;

    private Phone myPhone;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
        this.myPhone = new Phone();
        this.myPhone.number = 12345;
    }
}   

class Phone
{
    public long number;
}Langage du code : JavaScript (javascript)

Observez l’exemple ci-dessus : la classe Person contient les types natifs age et name, ainsi qu’un type personnalisé Phone. Le type Phone lui-même embarque le type primitif long pour stocker les numéros de téléphone portable.

Le lecteur pourrait voir le int age dans Person ou le long number dans Phone et supposer que ce sont des types valeur donc stockés sur la pile, mais ce n’est pas le cas. Même s’il s’agit de types primitifs intégrés, en tant que membres d’une classe personnalisée, ils sont stockés sur le tas avec l’instance de l’objet. La disposition mémoire finale est la suivante :

On voit sur le schéma que int age et long number sont des types valeur natifs, mais en tant que membres d’un type personnalisé ils résident sur le tas. Le champ myPhone contient une adresse de référence, ce qui prouve que les références des types de référence peuvent aussi être stockées sur le tas.

Règle clé : tous les membres de données des classes personnalisées sont stockés sur le tas, qu’il s’agisse de types valeur ou de types référence. Si une classe contient un membre de type référence comme myPhone dans l’exemple, ce champ ne conserve que l’adresse d’un autre objet situé sur le tas.

Si la classe Phone contenait un autre type personnalisé, une zone supplémentaire dans la mémoire de Phone stockerait l’adresse de référence de ce second objet.

L’exécution du programme débute dans la fonction Main. L’adresse de l’objet de référence de plus haut niveau est placée sur la pile, puis tous les objets liés forment une chaîne enchaînée sur le tas.

Les références forment une chaîne liée

Autre exemple

// Classe de type référence
class MyType
{
    public int A;       // Membre de type valeur, embarqué dans l’instance MyType sur le tas
    public Person B;    // Membre de type référence ; le pointeur de référence est sur le tas, les données propres sont allouées dans un bloc séparé du tas
}

class Person
{
    public string Name;
}

void Test()
{
    // La pile stocke le pointeur de référence de myObj
    MyType myObj = new MyType(); 
    /*
    Disposition du Tas :
    Objet MyType {
        A : valeur int intégrée directement
        B : pointeur de référence vers l’instance Person
    }
    Bloc indépendant du Tas : Person{ Name: ... }
    */

    // Variable locale de type valeur, allouée directement sur la pile
    int localNum = 10;
}Langage du code : PHP (php)

Les règles énoncées ci-dessus ne concernent que les membres de données de la classe, pas ses méthodes. Si une méthode de classe déclare des variables locales ou des objets de référence, les variables locales de type valeur et les adresses des instances de référence restent stockées sur la pile. Voir le code ci-dessous :

Examinez l’exemple suivant :

using System.ComponentModel.DataAnnotations;

class Program
{
    static void Main(string[] args)
    {
        int a = 10;
        Person bill = new Person("Bill", 18);
        bill.Calculate();
    }

}

class Person
{
    private string name;

    private int age;

    private Phone myPhone;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
        this.myPhone = new Phone();
        this.myPhone.number = 12345;
    }

    public int Calculate()
    {
        int one = 12;
        int two = 23;
        Phone myNewPhone = new Phone();
        return one + two;
    }
}   

class Phone
{
    public long number;
}Langage du code : PHP (php)

J’ai ajouté une méthode Calculate à la classe Person dans ce code. La méthode contient des variables locales à la fois de type valeur et de type référence, appelées depuis Main. Ces variables ne sont pas des membres de Person mais des variables locales propres à la méthode : les types valeur locaux sont sur la pile, et les adresses de référence des objets locaux aussi.

Lors de l’exécution de la ligne bill.Calculate(); la disposition mémoire est celle du schéma suivant

La zone bleue du schéma correspond à l’emplacement mémoire des variables internes de la méthode pendant son exécution.

Dès que la méthode termine son exécution, ces variables locales sont dépilées et la structure mémoire devient la suivante :

Une fois que Calculate a fini et que le flux retourne dans Main, toutes les variables créées au sein de la méthode sont supprimées de la pile. En revanche, le corps de données de myNewPhone (la zone bleue du schéma) reste sur le tas. Seule l’adresse de référence est retirée de la pile ; les données réelles ne sont pas détruites immédiatement et attendent que le ramasse-miettes GC les récupère à un moment ultérieur.

Types de valeur et Types de référence

Previous:

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *