Como a memória gerenciada .net lida com tipos de valor dentro de objetos?
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:
- A referência m fica na pilha e sai do escopo quando MyMethod() sai.
- O tipo de valor newID fica na pilha e sai do escopo quando MyMethod() sai.
- 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:
- Os tipos de valor dentro dos objetos residem na pilha ou no heap?
- Os tipos de valor boxing/unboxing em um objeto são uma preocupação?
- 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:
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
- Quaisquer referências ou tipos de valor que um objeto possui residem no heap.
- 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.