Tipos de Valor e Tipos de Referência

O tipo de um item de dados define o tamanho de memória necessário para armazená-lo (quantidade de bits) e também os membros de dados contidos nesse tipo. Além disso, o tipo determina se o objeto será alocado na memória na Pilha (Stack) ou no Heap (Montante).

Os tipos são divididos em duas categorias principais: Tipos de Valor (Value Types) e Tipos de Referência (Reference Types). A forma como esses dois grupos de objetos são armazenados na memória é completamente diferente.

1. Um tipo de valor ocupa apenas um bloco contínuo de memória, onde os dados reais são salvos diretamente.

Dados de tipo valor ficam diretamente armazenados na Pilha Stack. O campo Data guarda o valor bruto; por exemplo, com int age = 12; o número 12 é salvo diretamente nessa área da pilha

2. Tipos de referência precisam de dois segmentos separados de memória:

  • O primeiro segmento guarda os dados originais do objeto e esse espaço sempre é alocado no Heap;
  • O segundo segmento é um endereço de referência que aponta para a posição dos dados no heap. (A pilha armazena o endereço do heap; na época da linguagem C isso era chamado de ponteiro)
Os dados principais de um tipo de referência ficam no heap e recebem um endereço único como 000001, enquanto a pilha guarda esse endereço de referência Reference

As duas figuras acima só valem para objetos independentes, que não são membros de dados de outro objeto.

Observação Importante

A parte de dados propriamente dita de qualquer objeto de tipo referência sempre fica no heap, como mostrado nas ilustrações.

Entretanto, instâncias de tipo valor ou endereços de referência de tipos referência podem estar tanto na pilha quanto no heap, depende de como o código foi estruturado.

Exemplo:

Suponha que exista um tipo de referência Person com dois membros internos: um de tipo valor e outro de tipo referência.

Muitos desenvolvedores acham erroneamente que membros de tipo valor ficam na pilha, endereços de referência também na pilha e os dados do objeto no heap (como nas imagens), mas essa afirmação está incorreta.

Guarde uma regra fundamental: todos os dados originais de uma instância de tipo referência são alocados permanentemente no heap. Ambos os membros pertencem aos dados do objeto, então sejam eles tipo valor ou referência, serão armazenados juntos no heap.

Código de exemplo:

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;
}Code language: JavaScript (javascript)

Veja o exemplo acima: a classe Person tem os tipos nativos age e name, além de um tipo personalizado Phone. O tipo Phone, por sua vez, contém o tipo primitivo long para armazenar números de celular.

O leitor pode ver o int age dentro de Person ou o long number dentro de Phone e imaginar que por serem tipos valor ficam na pilha, mas não é o caso. Mesmo sendo tipos básicos embutidos, ao serem membros de uma classe personalizada, ficam salvos no heap junto com a instância do objeto. A disposição final da memória é a seguinte:

Na figura é possível ver que int age e long number são tipos valor nativos, mas como membros de um tipo personalizado ficam no heap. O campo myPhone guarda um endereço de referência, o que prova que até referências de tipos referência podem ser armazenadas no heap.

Regra principal: todos os membros de dados de classes personalizadas ficam no heap, não importa se são tipo valor ou referência. Se houver um membro de tipo referência dentro da classe, como o myPhone do exemplo, esse campo só guarda o endereço de outro objeto existente no heap.

Se a classe Phone tiver outro tipo personalizado, a área de memória do Phone terá um espaço extra para guardar o endereço de referência desse segundo objeto.

A execução do programa começa na função Main. O endereço do objeto de referência de nível mais alto fica na pilha, e todos os objetos subsequentes se conectam em cadeia no heap.

As referências formam uma cadeia interligada

Outro exemplo

// Classe de tipo referência
class MyType
{
    public int A;       // Membro tipo valor, embutido dentro da instância MyType no heap
    public Person B;    // Membro tipo referência; o ponteiro de referência fica no heap, os dados principais são alocados em um bloco separado do heap
}

class Person
{
    public string Name;
}

void Test()
{
    // A pilha guarda o ponteiro de referência de myObj
    MyType myObj = new MyType(); 
    /*
    Layout do Heap:
    Objeto MyType {
        A: valor int embutido diretamente
        B: ponteiro de referência apontando para a instância Person
    }
    Bloco independente do Heap: Person{ Name: ... }
    */

    // Variável local de tipo valor, alocada diretamente na pilha
    int localNum = 10;
}Code language: PHP (php)

As regras descritas valem apenas para os membros de dados da classe, não para seus métodos. Se um método da classe declarar variáveis locais ou objetos de referência, as variáveis locais de tipo valor e os endereços das instâncias de referência continuarão na pilha. Confira o código abaixo:

Veja o exemplo a seguir:

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;
}Code language: PHP (php)

Nesse código adicionei o método Calculate à classe Person. Dentro do método existem variáveis locais tanto de tipo valor quanto de referência, chamadas a partir do Main. Essas variáveis não são membros da Person, e sim variáveis locais exclusivas do método: as de tipo valor ficam na pilha, e os endereços de referência dos objetos locais também são armazenados na pilha.

Ao executar a linha bill.Calculate(); a disposição da memória fica como na figura abaixo

A área azul da figura representa o local onde as variáveis internas do método ficam armazenadas durante sua execução.

Quando o método terminar de rodar, todas essas variáveis locais são retiradas da pilha e a estrutura de memória passa a ser esta:

Após o término do Calculate, o fluxo volta para o Main, e todas as variáveis criadas dentro do método são removidas da pilha. Porém, a parte de dados do myNewPhone (a área azul da figura) permanece no heap. Apenas o endereço de referência sai da pilha; os dados reais não são destruídos imediatamente e aguardam o coletor de lixo GC recuperá-los em um momento oportuno.

Tipos de Valor e Tipos de Referência

Previous:
Next:

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *