Tipos de Valor y Tipos de Referencia

El tipo de elemento de datos determina el tamaño de memoria necesario para almacenarlo (cantidad de bits), así como los miembros de datos que contiene dicho tipo. Además, el tipo define si el objeto se aloja en memoria en la Pila (Stack) o en el Montón (Heap).

Los tipos se dividen en dos grandes categorías: Tipos de Valor (Value Types) y Tipos de Referencia (Reference Types). Ambos tipos de objetos usan mecanismos de almacenamiento en memoria totalmente distintos.

1. Un tipo de valor ocupa un único bloque de memoria, donde se guardan directamente los datos reales.

Los datos de tipo valor se almacenan directamente en la Pila Stack. El campo Data guarda el valor en bruto; por ejemplo, con int age = 12; el número 12 se guarda directamente en esa zona de la pila

2. Los tipos de referencia requieren dos segmentos independientes de memoria:

  • El primer segmento alberga los datos reales del objeto y este espacio se asigna siempre en el Montón;
  • El segundo segmento es una dirección de referencia que apunta a la ubicación de los datos en el montón. (La pila almacena la dirección del montón; en la época del lenguaje C esto se denominaba puntero)
Los datos principales de un tipo de referencia residen en el montón, recibiendo una dirección única como 000001, mientras que la pila guarda dicha dirección de referencia Reference

Las dos ilustraciones anteriores solo son válidas cuando el objeto es independiente y no forma parte de los miembros de datos de otro objeto.

Aviso Importante

La porción de datos propia de cualquier objeto de tipo referencia se almacena indefectiblemente en el montón, tal como se ve en las imágenes.

Sin embargo, las instancias de tipo valor o las direcciones de referencia de los tipos referencia pueden estar tanto en la pila como en el montón, en función de cómo esté estructurado el código.

Ejemplo:

Supongamos que existe un tipo de referencia Person con dos miembros internos: uno de tipo valor y otro de tipo referencia.

Muchos desarrolladores cometen el error de creer que los miembros de tipo valor van en la pila, las direcciones de referencia también en la pila y los datos del objeto en el montón (como en las imágenes), pero esta premisa es incorrecta.

Recuerda una regla fundamental: todos los datos propios de una instancia de tipo referencia se asignan permanentemente en el montón. Ambos miembros pertenecen a los datos del objeto, así que independientemente de si son tipo valor o referencia, se guardan juntos en el montón.

Código de ejemplo:

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;
}Lenguaje del código: JavaScript (javascript)

Observa el código anterior: la clase Person cuenta con los tipos nativos age y name, además de un tipo personalizado Phone. A su vez, el tipo Phone incluye el tipo primitivo long para almacenar números de teléfono móvil.

El lector podría ver el int age dentro de Person o el long number dentro de Phone y suponer que al ser tipos valor se guardan en la pila, pero no es así. Aunque sean tipos básicos integrados, al ser miembros de una clase personalizada se almacenan dentro del montón junto a la instancia del objeto. La disposición final en memoria es la siguiente:

Se aprecia en la imagen que int age y long number son tipos valor nativos, pero al ser miembros de un tipo personalizado residen en el montón. El campo myPhone guarda una dirección de referencia, lo que demuestra que las referencias de tipos referencia también pueden almacenarse en el montón.

Regla clave: todos los miembros de datos de clases personalizadas se guardan en el montón, sin importar si son tipo valor o referencia. Si dentro de una clase hay un miembro de tipo referencia como myPhone del ejemplo, dicho campo solo almacena la dirección de otro objeto ubicado en el montón.

Si la clase Phone contuviera otro tipo personalizado, el bloque de memoria de Phone reservaría un espacio adicional para guardar la dirección de referencia de ese segundo objeto.

La ejecución del programa empieza en la función Main. La dirección del objeto de referencia de nivel superior se guarda en la pila, y todos los objetos relacionados se enlazan en cadena dentro del montón.

Las referencias forman una cadena conectada

Otro ejemplo

// Clase de tipo referencia
class MyType
{
    public int A;       // Miembro tipo valor, integrado dentro de la instancia MyType en el montón
    public Person B;    // Miembro tipo referencia; el puntero de referencia se guarda en el montón, los datos propios se asignan en otro bloque separado del montón
}

class Person
{
    public string Name;
}

void Test()
{
    // La pila almacena el puntero de referencia de myObj
    MyType myObj = new MyType(); 
    /*
    Disposición del Montón:
    Objeto MyType {
        A: valor int embebido directamente
        B: puntero de referencia hacia la instancia Person
    }
    Bloque independiente del montón: Person{ Name: ... }
    */

    // Variable local de tipo valor, asignada directamente en la pila
    int localNum = 10;
}Lenguaje del código: PHP (php)

Las reglas descritas solo afectan a los miembros de datos de la clase, no a sus métodos. Si un método de clase declara variables locales u objetos de referencia, las variables locales de tipo valor y las direcciones de las instancias de referencia siguen guardándose en la pila. Consulta el código siguiente:

Revisa el siguiente ejemplo:

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;
}Lenguaje del código: PHP (php)

En este código he añadido el método Calculate a la clase Person. El método contiene variables locales tanto de tipo valor como de referencia, y se invoca desde Main. Estas variables no son miembros de Person, sino variables locales exclusivas del método: las de tipo valor se guardan en la pila, y las direcciones de referencia de los objetos locales también residen en la pila.

Cuando se ejecuta la línea bill.Calculate(); la disposición de memoria es la que se muestra a continuación

La zona azul de la imagen representa el lugar donde se almacenan las variables internas del método durante su ejecución.

En cuanto finaliza la ejecución del método, todas estas variables locales se retiran de la pila y la estructura de memoria queda así:

Una vez terminado Calculate y el flujo vuelve a Main, todas las variables creadas dentro del método se eliminan de la pila. Sin embargo, el cuerpo de datos de myNewPhone (la zona azul de la imagen) permanece en el montón. Solo se retira la dirección de referencia de la pila; los datos reales no se destruyen inmediatamente y se mantienen en el montón hasta que el recolector de basura GC los libere en algún momento.

Tipos de Valor y Tipos de Referencia

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *