Como a memória gerenciada .net lida com tipos de valor dentro de objetos?

StackOverflow https://stackoverflow.com/questions/24829

  •  09-06-2019
  •  | 
  •  

Pergunta

public class MyClass
{
    public int Age;
    public int ID;
}

public void MyMethod() 
{
    MyClass m = new MyClass();
    int newID;
}

No meu entendimento, o seguinte é verdadeiro:

  1. A referência m fica na pilha e sai do escopo quando MyMethod() sai.
  2. O tipo de valor newID fica na pilha e sai do escopo quando MyMethod() sai.
  3. O objeto criado pelo operador new reside no heap e se torna recuperável pelo GC quando MyMethod() sai, assumindo que nenhuma outra referência ao objeto exista.

Aqui está minha pergunta:

  1. Os tipos de valor dentro dos objetos residem na pilha ou no heap?
  2. Os tipos de valor boxing/unboxing em um objeto são uma preocupação?
  3. Existem recursos detalhados, mas compreensíveis, sobre este tópico?

Logicamente, eu acho que os tipos de valor dentro das classes estariam na pilha, mas não tenho certeza se eles precisam ser encaixotados para chegar lá.

Editar:

Leitura sugerida para este tema:

  1. CLR Via C# por Jeffrey Richter
  2. .NET essencial por Don Box
Foi útil?

Solução

Valores de tipo de valor para uma classe ter para conviver com a instância do objeto no heap gerenciado.A pilha do thread para um método dura apenas enquanto durar o método;como o valor pode persistir se só existe nessa pilha?

O tamanho do objeto de uma classe no heap gerenciado é a soma de seus campos de tipo de valor, ponteiros de tipo de referência e variáveis ​​adicionais de sobrecarga CLR, como o índice de bloco Sync.Quando se atribui um valor ao campo de tipo de valor de um objeto, o CLR copia o valor para o espaço alocado dentro do objeto para aquele campo específico.

Tomemos por exemplo uma classe simples com um único campo.

public class EmbeddedValues
{
  public int NumberField;
}

E com isso, uma aula de teste simples.

public class EmbeddedTest
{
  public void TestEmbeddedValues()
  {
    EmbeddedValues valueContainer = new EmbeddedValues();

    valueContainer.NumberField = 20;
    int publicField = valueContainer.NumberField;
  }
}

Se você usar o MSIL Disassembler fornecido pelo .NET Framework SDK para espiar o código IL de EmbeddedTest.TestEmbeddedValues()

.method public hidebysig instance void  TestEmbeddedValues() cil managed
{
  // Code size       23 (0x17)
  .maxstack  2
  .locals init ([0] class soapextensions.EmbeddedValues valueContainer,
           [1] int32 publicField)
  IL_0000:  nop
  IL_0001:  newobj     instance void soapextensions.EmbeddedValues::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.s   20
  IL_000a:  stfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_000f:  ldloc.0
  IL_0010:  ldfld      int32 soapextensions.EmbeddedValues::NumberField
  IL_0015:  stloc.1
  IL_0016:  ret
} // end of method EmbeddedTest::TestEmbeddedValues

Observe que o CLR está sendo instruído a stfld o valor carregado de "20" na pilha para o local do campo NumberField do EmbeddValues ​​carregado, diretamente no heap gerenciado.Da mesma forma, ao recuperar o valor, ele usa ldfld instrução para copiar diretamente o valor desse local de heap gerenciado para a pilha de threads.Nenhuma caixa/desembalagem acontece com esses tipos de operações.

Outras dicas

  1. Quaisquer referências ou tipos de valor que um objeto possui residem no heap.
  2. Somente se você estiver lançando entradas para objetos.

O melhor recurso que vi para isso é o livro CLR via C# de Jeffrey Richter.Vale a pena ler se você fizer algum desenvolvimento em .NET.Com base nesse texto, meu entendimento é que os tipos de valor dentro de um tipo de referência residem no heap incorporado no objeto pai.Os tipos de referência são sempre na pilha.Boxing e unboxing não são simétricos.O boxe pode ser uma preocupação maior do que o unboxing.Boxe vai requer a cópia do conteúdo do tipo de valor da pilha para o heap.Dependendo da frequência com que isso acontece com você, pode não haver sentido em ter uma estrutura em vez de uma classe.Se você tiver algum código crítico de desempenho e não tiver certeza se o boxe e o unboxing estão acontecendo, use uma ferramenta para examinar o código IL do seu método.Você verá as palavras box e unbox no IL.Pessoalmente, eu mediria o desempenho do meu código e só então veria se isso é motivo de preocupação.No seu caso, não acho que isso seja uma questão tão crítica.Você não precisará copiar da pilha para o heap (caixa) toda vez que acessar esse tipo de valor dentro do tipo de referência.É nesse cenário que o boxe se torna um problema mais significativo.

  • Resposta nº 1:Pilha.Parafraseando Don Box de seu excelente 'Essential .Net Vol 1'

Tipos de referência (RT) sempre geram instâncias alocadas no heap. Por outro lado, os tipos de valor (VT) dependem do contexto - se uma var local for um VT, o CLR aloca memória na pilha.Se um campo em uma classe for membro de um VT, então o CLR aloca memória para a instância como parte do layout do objeto/Tipo no qual o campo é declarado.

  • Resposta nº 2:Não.O boxe ocorreria apenas quando você acessasse uma estrutura por meio de uma referência de objeto/ponteiro de interface. obInstance.VT_typedfield não vai encaixotar.

    Variáveis ​​RT contém o endereço do objeto ao qual se refere.2 RT var pode apontar para o mesmo objeto. Em contraste, as variáveis ​​VT são as próprias instâncias.2 VT var não pode apontar para o mesmo objeto (estrutura)

  • Resposta nº 3:Essential .net de Don Box / CLR de Jeffrey Richter via C#.Tenho uma cópia do antigo...embora o último possa ser mais atualizado para revisões .Net

Os tipos de valor dentro dos objetos residem na pilha ou no heap?

Na pilha.Eles fazem parte da alocação da área ocupada pelo objeto, assim como seriam os ponteiros para conter referências.

Os tipos de valor boxing/unboxing em um objeto são uma preocupação?

Não há boxe aqui.

Existem recursos detalhados, mas compreensíveis, sobre este tópico?

+1 voto no livro de Richter.

Uma variável ou outro local de armazenamento de um tipo de estrutura é uma agregação dos campos de instância pública e privada desse tipo.Dado

struct Foo {public int x,y; int z;}

uma declaração Foo bar; vai causar bar.x, bar.y, e bar.z para ser armazenado em qualquer lugar bar será armazenado.Adicionando tal declaração de bar a uma classe será, do ponto de vista do layout de armazenamento, equivalente a adicionar três int Campos.Na verdade, se alguém nunca fez nada com bar exceto acessar seus campos, os campos de bar se comportaria da mesma forma que três campos bar_x, bar_y, e bar_cantaccessthis_z [acessar o último exigiria fazer coisas com bar além de acessar seus campos, mas ocuparia espaço, independentemente de ser usado ou não para alguma coisa].

Reconhecer locais de armazenamento do tipo estrutura como agregações de campos é o primeiro passo para compreender as estruturas.Tentar vê-los segurando algum tipo de objeto pode parecer "mais simples", mas não corresponde ao modo como eles realmente funcionam.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top