Matrices, montón y pila y tipos de valor

134
int[] myIntegers;
myIntegers = new int[100];

En el código anterior, ¿el nuevo int [100] genera la matriz en el montón? Por lo que he leído en CLR a través de c #, la respuesta es sí. Pero lo que no puedo entender es qué sucede con los int reales dentro de la matriz. Como son tipos de valor, supongo que tendrían que estar encuadrados, ya que puedo, por ejemplo, pasar mis Integer a otras partes del programa y se desordenaría si se dejaran en él todo el tiempo . ¿O estoy equivocado? Supongo que solo estarían en caja y vivirían en el montón mientras existiera la matriz.

elysium devorado
fuente

Respuestas:

289

Su matriz se asigna en el montón y las entradas no están encuadradas.

La fuente de su confusión es probable porque la gente ha dicho que los tipos de referencia se asignan en el montón y los tipos de valor se asignan en la pila. Esta no es una representación completamente precisa.

Todas las variables y parámetros locales se asignan en la pila. Esto incluye tanto los tipos de valor como los tipos de referencia. La diferencia entre los dos es solo lo que se almacena en la variable. Como era de esperar, para un tipo de valor, el valor del tipo se almacena directamente en la variable, y para un tipo de referencia, el valor del tipo se almacena en el montón, y una referencia a este valor es lo que se almacena en la variable.

Lo mismo vale para los campos. Cuando se asigna memoria para una instancia de un tipo agregado (a classo a struct), debe incluir almacenamiento para cada uno de sus campos de instancia. Para los campos de tipo de referencia, este almacenamiento contiene solo una referencia al valor, que luego se asignaría en el montón. Para los campos de tipo de valor, este almacenamiento contiene el valor real.

Entonces, dados los siguientes tipos:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

Los valores de cada uno de estos tipos requerirían 16 bytes de memoria (suponiendo un tamaño de palabra de 32 bits). El campo Ien cada caso toma 4 bytes para almacenar su valor, el campo Stoma 4 bytes para almacenar su referencia y el campo Ltoma 8 bytes para almacenar su valor. Entonces la memoria para el valor de ambos RefTypey se ValTypeve así:

 0 ┌───────────────────┐
   │ yo │
 4 ├───────────────────┤
   │ S │
 8 ├───────────────────┤
   │ L │
   │ │
16 └───────────────────┘

Ahora bien, si usted tenía tres variables locales de una función, de tipos RefType, ValTypey int[], de esta manera:

RefType refType;
ValType valType;
int[]   intArray;

entonces su pila podría verse así:

 0 ┌───────────────────┐
   │ refType │
 4 ├───────────────────┤
   │ valType │
   │ │
   │ │
   │ │
20 ├───────────────────┤
   │ intArray │
24 └───────────────────┘

Si asignó valores a estas variables locales, así:

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

Entonces su pila podría verse así:

 0 ┌───────────────────┐
   X4 0x4A963B68 │ - dirección de montón de `refType`
 4 ├───────────────────┤
   │ 200 │ - valor de `valType.I`
   X4 0x4A984C10 │ - dirección del montón de `valType.S`
   X4 0x44556677 │ - bajo 32 bits de `valType.L`
   │ 0x00112233 │ - 32 bits altos de `valType.L`
20 ├───────────────────┤
   X4 0x4AA4C288 │ - dirección de montón de `intArray`
24 └───────────────────┘

La memoria en la dirección 0x4A963B68(valor de refType) sería algo así como:

 0 ┌───────────────────┐
   │ 100 │ - valor de `refType.I`
 4 ├───────────────────┤
   │ 0x4A984D88 │ - dirección de montón de `refType.S`
 8 ├───────────────────┤
   │ 0x89ABCDEF │ - bajo 32 bits de `refType.L`
   │ 0x01234567 │ - 32 bits altos de `refType.L`
16 └───────────────────┘

La memoria en la dirección 0x4AA4C288(valor de intArray) sería algo así como:

 0 ┌───────────────────┐
   │ 4 │ - longitud de la matriz
 4 ├───────────────────┤
   │ 300 │ - `intArray [0]`
 8 ├───────────────────┤
   │ 301 │ - `intArray [1]`
12 ├───────────────────┤
   │ 302 │ - `intArray [2]`
16 ├───────────────────┤
   │ 303 │ - `intArray [3]`
20 └───────────────────┘

Ahora, si pasó intArraya otra función, el valor introducido en la pila sería 0x4AA4C288, la dirección de la matriz, no una copia de la matriz.

Papi
fuente
52
Observo que la afirmación de que todas las variables locales se almacenan en la pila es inexacta. Las variables locales que son variables externas de una función anónima se almacenan en el montón. Las variables locales de los bloques iteradores se almacenan en el montón. Las variables locales de bloques asíncronos se almacenan en el montón. Las variables locales que se registran no se almacenan en la pila ni en el montón. Las variables locales que se eliden no se almacenan en la pila ni en el montón.
Eric Lippert
55
LOL, siempre el piojo, Sr. Lippert. :) Me siento obligado a señalar que, con la excepción de sus dos últimos casos, los llamados "locales" dejan de ser locales en el momento de la compilación. La implementación los eleva al estado de los miembros de la clase, que es la única razón por la que se almacenan en el montón. Entonces es simplemente un detalle de implementación (risita). Por supuesto, el almacenamiento de registros es un detalle de implementación de nivel aún más bajo, y elision no cuenta.
P Daddy
3
Por supuesto, toda mi publicación son detalles de implementación, pero, como estoy seguro de que te das cuenta, todo fue un intento de separar los conceptos de variables y valores . Una variable (llamada local, un campo, un parámetro, lo que sea) se puede almacenar en la pila, el montón o en algún otro lugar definido por la implementación, pero eso no es realmente lo importante. Lo importante es si esa variable almacena directamente el valor que representa, o simplemente una referencia a ese valor, almacenado en otro lugar. Es importante porque afecta a la semántica de copia: si la copia de esa variable copia su valor o su dirección.
P Daddy
16
Aparentemente tienes una idea diferente de lo que significa ser una "variable local" que yo. Parece creer que una "variable local" se caracteriza por sus detalles de implementación . Esta creencia no está justificada por nada de lo que tenga conocimiento en la especificación de C #. Una variable local es, de hecho, una variable declarada dentro de un bloque cuyo nombre solo está dentro del alcance del espacio de declaración asociado con el bloque. Le aseguro que las variables locales que, como detalle de implementación, se izan a los campos de una clase de cierre, siguen siendo variables locales de acuerdo con las reglas de C #.
Eric Lippert
15
Dicho esto, por supuesto, su respuesta es generalmente excelente; El punto de que los valores son conceptualmente diferentes de las variables es uno que debe hacerse con la mayor frecuencia y volumen posible, ya que es fundamental. ¡Y sin embargo, muchas personas creen en los mitos más extraños sobre ellos! Tan bueno contigo por pelear la buena batalla.
Eric Lippert
23

Sí, la matriz se ubicará en el montón.

Las entradas dentro de la matriz no se encuadrarán. El hecho de que exista un tipo de valor en el montón no significa necesariamente que estará encuadrado. El boxeo solo ocurrirá cuando se asigne un tipo de valor, como int, a una referencia de objeto de tipo.

Por ejemplo

No en caja:

int i = 42;
myIntegers[0] = 42;

Cajas:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

También puede consultar la publicación de Eric sobre este tema:

JaredPar
fuente
1
Pero no lo entiendo. ¿No deberían asignarse los tipos de valor en la pila? ¿O tanto los tipos de valor como los de referencia se pueden asignar tanto en el montón como en la pila y es solo que generalmente se almacenan en un lugar u otro?
devorado elysium
44
@Jorge, un tipo de valor sin contenedor / contenedor de tipo de referencia vivirá en la pila. Sin embargo, una vez que se usa dentro de un contenedor de tipo de referencia, vivirá en el montón. Una matriz es un tipo de referencia y, por lo tanto, la memoria para el int debe estar en el montón.
JaredPar
2
@Jorge: los tipos de referencia viven solo en el montón, nunca en la pila. Por el contrario, es imposible (en un código verificable) almacenar un puntero a una ubicación de pila en un objeto de un tipo de referencia.
Anton Tykhyy
1
Creo que querías asignar i a arr [0]. La asignación constante seguirá causando el boxeo de "42", pero usted creó i, por lo que también puede usarlo ;-)
Marcus
@AntonTykhyy: No hay una regla que sepa que un CLR no puede escapar al análisis. Si detecta que un objeto nunca será referenciado después de la vida útil de la función que lo creó, es completamente legítimo, e incluso preferible, construir el objeto en la pila, ya sea un tipo de valor o no. El "tipo de valor" y el "tipo de referencia" básicamente describen lo que está en la memoria ocupada por la variable, no una regla rígida y rápida sobre dónde vive el objeto.
cHao
21

Para entender lo que está sucediendo, aquí hay algunos hechos:

  • Los objetos siempre se asignan en el montón.
  • El montón solo contiene objetos.
  • Los tipos de valor se asignan en la pila o son parte de un objeto en el montón.
  • Una matriz es un objeto.
  • Una matriz solo puede contener tipos de valor.
  • Una referencia de objeto es un tipo de valor.

Por lo tanto, si tiene una matriz de enteros, la matriz se asigna en el montón y los enteros que contiene es parte del objeto de matriz en el montón. Los enteros residen dentro del objeto de matriz en el montón, no como objetos separados, por lo que no están encuadrados.

Si tiene una matriz de cadenas, es realmente una matriz de referencias de cadena. Como las referencias son tipos de valor, formarán parte del objeto de matriz en el montón. Si coloca un objeto de cadena en la matriz, en realidad coloca la referencia al objeto de cadena en la matriz, y la cadena es un objeto separado en el montón.

Guffa
fuente
Sí, las referencias se comportan exactamente como los tipos de valor, pero noté que generalmente no se llaman de esa manera, ni se incluyen en los tipos de valor. Ver por ejemplo (pero hay muchos más como este) msdn.microsoft.com/en-us/library/s1ax56ch.aspx
Henk Holterman
@Henk: Sí, tiene razón en que las referencias no se enumeran entre las variables de tipo de valor, pero cuando se trata de cómo se les asigna la memoria, son tipos de valores en todos los aspectos, y es muy útil darse cuenta de eso para comprender cómo se asigna la memoria Todo encaja. :)
Guffa el
Dudo del quinto punto, "Una matriz solo puede contener tipos de valores". ¿Qué pasa con la matriz de cadenas? cadena [] cadenas = nueva cadena [4];
Sunil Purushothaman
9

Creo que en el centro de su pregunta se encuentra un malentendido sobre los tipos de referencia y valor. Esto es algo con lo que probablemente lucharon todos los desarrolladores de .NET y Java.

Una matriz es solo una lista de valores. Si se trata de una matriz de un tipo de referencia (por ejemplo, a string[]), la matriz es una lista de referencias a varios stringobjetos en el montón, ya que una referencia es el valor de un tipo de referencia. Internamente, estas referencias se implementan como punteros a una dirección en la memoria. Si desea visualizar esto, una matriz de este tipo se vería así en la memoria (en el montón):

[ 00000000, 00000000, 00000000, F8AB56AA ]

Esta es una matriz de stringque contiene 4 referencias a stringobjetos en el montón (los números aquí son hexadecimales). Actualmente, solo el último stringrealmente apunta a algo (la memoria se inicializa a todos los ceros cuando se asigna), esta matriz básicamente sería el resultado de este código en C #:

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

La matriz anterior estaría en un programa de 32 bits. En un programa de 64 bits, las referencias serían dos veces más grandes ( F8AB56AAserían 00000000F8AB56AA).

Si tiene una matriz de tipos de valores (digamos an int[]), la matriz es una lista de enteros, ya que el valor de un tipo de valor es el valor en sí mismo (de ahí el nombre). La visualización de tal matriz sería esta:

[ 00000000, 45FF32BB, 00000000, 00000000 ]

Esta es una matriz de 4 enteros, donde solo al segundo int se le asigna un valor (a 1174352571, que es la representación decimal de ese número hexadecimal) y el resto de los enteros sería 0 (como dije, la memoria se inicializa a cero y 00000000 en hexadecimal es 0 en decimal). El código que produjo esta matriz sería:

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

Esta int[]matriz también se almacenaría en el montón.

Como otro ejemplo, la memoria de una short[4]matriz se vería así:

[ 0000, 0000, 0000, 0000 ]

Como el valor de a shortes un número de 2 bytes.

Cuando se almacena un tipo de valor, es solo un detalle de implementación, como Eric Lippert explica muy bien aquí , no es inherente a las diferencias entre los tipos de valor y referencia (que es la diferencia en el comportamiento).

Cuando pasa algo a un método (ya sea un tipo de referencia o un tipo de valor), una copia del valor del tipo se pasa realmente al método. En el caso de un tipo de referencia, el valor es una referencia (piense en esto como un puntero a una pieza de memoria, aunque eso también es un detalle de implementación) y en el caso de un tipo de valor, el valor es la cosa misma.

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

El boxeo solo ocurre si convierte un tipo de valor en un tipo de referencia. Este código recuadros:

object o = 5;
JulianR
fuente
Creo que "un detalle de implementación" debería ser un tamaño de fuente: 50px. ;)
sisve
2

Estas son ilustraciones que representan la respuesta anterior de @P Daddy

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

E ilustré los contenidos correspondientes en mi estilo.

ingrese la descripción de la imagen aquí

Parque YoungMin
fuente
@P Daddy hice ilustraciones. Por favor, compruebe si hay una parte incorrecta. Y tengo algunas preguntas adicionales. 1. Cuando creo una matriz de tipo int de 4 longitudes, la información de longitud (4) también siempre se almacena en la memoria.
YoungMin Park
2. En la segunda ilustración, ¿dónde se almacena la dirección de matriz copiada? ¿Es la misma área de pila en la que se almacena la dirección intArray? ¿Es otra pila pero el mismo tipo de pila? ¿Es diferente tipo de pila? 3. ¿Qué significa bajo de 32 bits / alto de 32 bits? 4. ¿Cuál es el valor de retorno cuando asigno el tipo de valor (en este ejemplo, estructura) en la pila usando una nueva palabra clave? ¿Es también la dirección? Cuando estaba revisando esta declaración Console.WriteLine (valType), mostraba el nombre completo como un objeto como ConsoleApp.ValType.
YoungMin Park
5. valType.I = 200; ¿Esta afirmación significa que obtengo la dirección de valType, por esta dirección accedo al I y allí guardo 200 pero "en la pila".
YoungMin Park
1

Se asigna una matriz de enteros en el montón, nada más y nada menos. Las referencias de myIntegers al comienzo de la sección donde se asignan las entradas. Esa referencia se encuentra en la pila.

Si tiene una matriz de objetos de tipo de referencia, como el tipo de Objeto, myObjects [], ubicado en la pila, haría referencia al conjunto de valores que hacen referencia a los objetos mismos.

En resumen, si pasa myIntegers a algunas funciones, solo pasa la referencia al lugar donde se asigna el grupo real de enteros.

Dykam
fuente
1

No hay boxeo en su código de ejemplo.

Los tipos de valor pueden vivir en el montón como lo hacen en su conjunto de entradas. La matriz se asigna en el montón y almacena entradas, que resultan ser tipos de valor. El contenido de la matriz se inicializa a default (int), que resulta ser cero.

Considere una clase que contiene un tipo de valor:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

La variable h se refiere a una instancia de HasAnInt que vive en el montón. Simplemente contiene un tipo de valor. Eso está perfectamente bien, 'yo' simplemente sucede que vive en el montón ya que está contenido en una clase. No hay boxeo en este ejemplo tampoco.

Curt Nichols
fuente
1

Todo el mundo ha dicho lo suficiente, pero si alguien está buscando una muestra clara (pero no oficial) y documentación sobre el montón, la pila, las variables locales y las variables estáticas, consulte el artículo completo de Jon Skeet sobre Memoria en .NET: ¿qué pasa? dónde

Extracto:

  1. Cada variable local (es decir, una declarada en un método) se almacena en la pila. Eso incluye variables de tipo de referencia: la variable en sí está en la pila, pero recuerde que el valor de una variable de tipo de referencia es solo una referencia (o nula), no el objeto en sí. Los parámetros del método también cuentan como variables locales, pero si se declaran con el modificador de referencia, no obtienen su propio espacio, sino que comparten un espacio con la variable utilizada en el código de llamada. Vea mi artículo sobre el paso de parámetros para más detalles.

  2. Las variables de instancia para un tipo de referencia siempre están en el montón. Ahí es donde el objeto mismo "vive".

  3. Las variables de instancia para un tipo de valor se almacenan en el mismo contexto que la variable que declara el tipo de valor. La ranura de memoria para la instancia contiene efectivamente las ranuras para cada campo dentro de la instancia. Eso significa (dados los dos puntos anteriores) que una variable de estructura declarada dentro de un método siempre estará en la pila, mientras que una variable de estructura que es un campo de instancia de una clase estará en el montón.

  4. Cada variable estática se almacena en el montón, independientemente de si se declara dentro de un tipo de referencia o un tipo de valor. Solo hay un espacio en total, sin importar cuántas instancias se creen. (Sin embargo, no es necesario que se cree ninguna instancia para que exista esa ranura). Los detalles de exactamente en qué montón viven las variables son complicados, pero se explican en detalle en un artículo de MSDN sobre el tema.

gmaran23
fuente