2010-04-01 7 views
13

Realmente agradecería si alguien me podría decir si he entendido bien:¿Las referencias de estos objetos están en la Pila o en el Heap?

class X 
{ 
    A a1=new A(); // reference on the stack, object value on the heap 
    a1.VarA=5; // on the stack - value type 
    A a2=a1;  // reference on the stack, object value on the heap 
    a2.VarA=10; // on the stack - value type   
} 

también ambos a1 y a2 referencias están en la pila, mientras que sus valores son "objeto" en el montón. ¿Pero qué pasa con la variable VarA, su tipo de valor sigue siendo puro?

class A 
{ 
    int VarA; 
} 
+3

Como este código no se compila es muy difícil describir cómo lo maneja el tiempo de ejecución. ¿Están todas esas declaraciones destinadas a estar dentro de un cuerpo de método? ¿Son esas declaraciones de campo o declaraciones de variables locales? –

Respuesta

27

Usted está haciendo preguntas acerca de los detalles de implementación , así que la respuesta dependerá de la aplicación particular. Vamos a considerar una versión de su programa que compila realidad:

class A { public int VarA; } 
class X 
{ 
    static void Main(string[] args) 
    { 
     A a1 = new A(); 
     a1.VarA = 5; 
     A a2 = a1; 
     a2.VarA = 10; 
    } 
} 

aquí es lo que ocurre en Microsoft de CLR 4.0, corriendo C# 4.0, en modo de depuración.

En este punto, el puntero del marco de pila se ha copiado en ebp registro:

Aquí asignar memoria del montón para el nuevo objeto.

A a1 = new A(); 
mov   ecx,382518h 
call  FFE6FD30 

Devuelve una referencia a un objeto de pila en eax. Almacenamos la referencia en la ranura de pila ebp-48, que es una ranura temporal no asociada a ningún nombre. Recuerde, a1 aún no se ha inicializado.

mov   dword ptr [ebp-48h],eax 

Ahora restamos que la referencia que acaba de almacenar en la pila y copiarlo en ecx, que será utilizado para el puntero "this" a la llamada a la ctor.

mov   ecx,dword ptr [ebp-48h] 

Ahora llamamos al ctor.

call  FFE8A518 

Ahora copiamos la referencia almacenada en la ranura temporal en la pila registro eax nuevo.

mov   eax,dword ptr [ebp-48h] 

Y ahora copiamos la referencia en eax en la ranura pila ebp-40, que es a1.

mov   dword ptr [ebp-40h],eax 

Ahora debe obtener a1 en eax:

a1.VarA = 5; 
mov   eax,dword ptr [ebp-40h] 

Recuerde, eax es ahora la dirección de los datos montón asignados para lo que hace referencia a1. El campo VarA de esa cosa es de cuatro bytes en el objeto, por lo que almacenar 5 en que:

mov   dword ptr [eax+4],5 

Ahora hacemos una copia de la referencia en la ranura de la pila para obtener a1 en EAX, y luego copia que en el ranura de pila para a2, que es ebp-44.

A a2 = a1; 
mov   eax,dword ptr [ebp-40h] 
mov   dword ptr [ebp-44h],eax 

Y ahora como era de esperar de nuevo conseguimos A2 en EAX y luego deferencia la referencia de cuatro bytes para escribir 0x0A en el VarA:

a2.VarA = 10; 
mov   eax,dword ptr [ebp-44h] 
mov   dword ptr [eax+4],0Ah 

Así que la respuesta a su pregunta es que las referencias al objeto se almacenan en la pila en tres lugares: ebp-44, ebp-48 y ebp-40. Se almacenan en registros en eax y ecx. La memoria del objeto, incluido su campo, se almacena en el montón administrado. Todo esto está en x86 en la compilación de depuración, del CLR v4.0 de Microsoft. Si desea saber cómo se almacenan las cosas en la pila, el montón y los registros en alguna otra configuración, podría ser completamente diferente. Todas las referencias se pueden almacenar en el montón, o en todos los registros; puede que no haya ninguna pila en absoluto. Depende totalmente de cómo los autores del compilador jit decidieron implementar la semántica de IL.

+0

También depende de cómo los autores del compilador de C# decidieron implementar la semántica de C#.Las variables locales ('a1' y' a2') podrían implementarse como campos en un tipo gestionado, dejando solo una referencia única en cada marco de pila. Me doy cuenta de que mencionar esto en un comentario de tu publicación invoca pensamientos de abuelas y de succión de huevos, pero pensé que podría mencionarlo de todos modos :) –

+0

@Jon: De hecho. Hay muy pocos errores que producimos durante la fase de generación IL del compilador; uno de ellos es "demasiados lugareños": no recuerdo cuál es el límite, pero es algo así como que no se pueden tener más de 32 K o 64 K locales o temporales en un método. (Obviamente, el código real no tiene este problema, pero el código generado por la máquina podría.) A menudo he pensado que, en tales casos, en lugar de producir un error, simplemente comience a elevarlos a los campos. Pero es un escenario demasiado oscuro para justificar el costo de escribir y probar el código. –

10

Estrictamente hablando, depende de la implementación. Por lo general, un desarrollador de .NET no debería preocuparse por esto. Hasta donde sé, en la implementación de Microsoft de .NET, las variables de los tipos de valores se almacenan en la pila (cuando se declaran dentro de un método) y los datos de los objetos de tipo referencia se asignan en un montón administrado. Pero, recuerde, cuando un tipo de valor es un campo de una clase, los datos de clase en sí se almacenan en un montón (incluidos todos los campos de tipo de valor). Por lo tanto, no mezcle la semántica (tipos de valores frente a los tipos de referencia) con las reglas de asignación. Estas cosas pueden estar correlacionadas o no.

2

que cree que puede tener un ligero malentendido ...

En términos generales, los tipos de referencia van en el montón, y los tipos de valor/locales que creo (puede ser malo) van a la pila. Sin embargo, los ejemplos A1.VarA y A2.VarA hacen referencia a un campo de un tipo de referencia, que se almacena junto con el objeto en el montón ...

+0

Sí, pero el valor de ese campo es int, por lo tanto, tipo de valor, ¿no? – Petr

+0

@Petr, todos los campos están contenidos en el tipo de referencia A, que está en el montón. –

2

En este caso, a1.VarA estaría en el montón como espacio para ello se habría asignado cuando lo hizo A a1 = new A().

Si usted acaba de hacer int i = 5; en una función que van a la pila, pero a medida que a1 que explícitamente se debía asignarse en el montón y luego se colocarán todos los tipos de valores asociados a ella en el montón

0

Leer Jeff Richter's CLR via C# para una comprensión completa de este tema.

0

Recuerde leer en C# en profundidad: - Solo las variables locales (el método declarado dentro) y el parámetro de método viven en la pila. Variable de instancia como varA en el caso anterior reside en el montón.

+3

Tenga en cuenta que las variables locales que son locales cerrados de un método lambda o anónimo no se almacenan en la pila en la implementación de Microsoft de C#. Lo mismo ocurre con las variables locales que están en un bloque iterador. –

2
class X 
{ 
    A a1=new A(); // reference on the stack, object value on the heap 
    a1.VarA=5; // on the Heap- value type (Since it is inside a reference type) 
    A a2=a1;  // reference on the stack, object value on the heap 
    a2.VarA=10; // on the Heap - value type (Since it is inside a reference type) 
} 
0

Soy nuevo en C# también. Tu pregunta es muy importante, también pensé en ello. Toda la documentación dice, los valores se acumulan y las referencias se vuelven geniales, pero como dicen los tipos anteriores, es solo para el código dentro de los métodos. En la etapa de aprendizaje, me doy cuenta de que todos los códigos de programa comienzan dentro de un método que pertenece a una instancia que pertenece a heap. Tan conceptual, la pila no es igual en términos de montón, como toda la documentación confunde a las personas. El mecanismo de pila se encuentra solo en un método ...

Cuestiones relacionadas