¿Por qué los libros .Net hablan sobre la asignación de memoria de pila frente a pila?

36

Parece que cada libro .net habla sobre los tipos de valor frente a los tipos de referencia y señala (a menudo incorrectamente) dónde se almacena cada tipo: el montón o la pila. Por lo general, se encuentra en los primeros capítulos y se presenta como un hecho muy importante. Creo que incluso está cubierto en los exámenes de certificación . ¿Por qué es importante apilar vs montón para los desarrolladores (principiantes) .Net? Usted asigna cosas y simplemente funciona, ¿verdad?

Greg
fuente
11
Algunos autores simplemente tienen un juicio realmente malo sobre lo que es importante enseñar a los principiantes y lo que es el ruido irrelevante. En un libro que vi recientemente, la primera mención de modificadores de acceso ya incluía protección interna , que nunca he usado en 6 años de C # ...
Timwi
1
Supongo que quien escribió la documentación original de .Net para esa parte hizo gran parte de ella, y esa documentación es en lo que los autores basaron originalmente sus libros, y luego se quedó.
Greg
Decir que los tipos de valores copian todo y las referencias no lo hacen, tendría más sentido y sería más fácil entender por qué usar referencias, ya que el lugar donde se almacenan esos valores puede ser específico de la implementación e incluso irrelevante.
Trinidad
Entrevista culto de carga?
Den

Respuestas:

37

Me estoy convenciendo de que la razón principal por la que esta información se considera importante es la tradición. En entornos no administrados, la distinción entre la pila y el montón es importante y tenemos que asignar y eliminar manualmente la memoria que usamos. Ahora, la recolección de basura se encarga de la gestión, por lo que ignoran esa parte. No creo que se haya transmitido realmente el mensaje de que tampoco tenemos que importarnos qué tipo de memoria se usa.

Como Fede señaló, Eric Lippert tiene algunas cosas muy interesantes que decir sobre esto: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

A la luz de esa información, podría ajustar mi primer párrafo para leer básicamente: "La razón por la cual las personas incluyen esta información y asumen que es importante es debido a información incorrecta o incompleta combinada con la necesidad de este conocimiento en el pasado".

Para aquellos que piensan que todavía es importante por razones de rendimiento: ¿Qué acciones tomarían para mover algo del montón a la pila si midieran las cosas y descubrieran que importaban? Lo más probable es que encuentre una forma completamente diferente de mejorar el rendimiento para el área problemática.

John Fisher
fuente
66
He oído que en algunas implementaciones de framework (compacto en Xbox específicamente) es mejor usar estructuras durante el período de renderizado (el juego en sí) para reducir la recolección de basura. Todavía usaría tipos normales en otros lugares, pero preasignado para que el GC no se ejecute durante el juego. Esa es la única optimización con respecto a la pila frente al montón que conozco en .NET, y es bastante específica para las necesidades del marco compacto y los programas en tiempo real.
CodexArcanum
55
Principalmente estoy de acuerdo con el argumento de la tradición. Muchos programadores experimentados en algún momento podrían haber programado en un lenguaje de bajo nivel donde esto importaba si quisieras un código correcto y eficiente. Sin embargo, tome C ++ por ejemplo, un lenguaje no administrado: la especificación oficial en realidad no dice que las variables automáticas deben ir a la pila, etc. El estándar de C ++ trata la pila y el montón como detalles de implementación. +1
stakx
36

Parece que cada libro de .NET habla sobre los tipos de valor frente a los tipos de referencia y señala (a menudo incorrectamente) dónde se almacena cada tipo: el montón o la pila. Por lo general, se encuentra en los primeros capítulos y se presenta como un hecho muy importante.

Concuerdo completamente; Veo esto todo el tiempo.

¿Por qué los libros .NET hablan sobre la asignación de memoria de pila frente a pila?

Una parte de la razón es porque muchas personas llegaron a C # (u otros lenguajes .NET) desde un fondo C o C ++. Dado que esos idiomas no le imponen las reglas sobre la vida útil del almacenamiento, debe conocer esas reglas e implementar su programa cuidadosamente para seguirlas.

Ahora, conocer esas reglas y seguirlas en C no requiere que comprenda "el montón" y "la pila". Pero si comprende cómo funcionan las estructuras de datos, a menudo es más fácil entender y seguir las reglas.

Al escribir un libro para principiantes, es natural que un autor explique los conceptos en el mismo orden en que los aprendió. Ese no es necesariamente el orden que tiene sentido para el usuario. Recientemente fui editor técnico del libro para principiantes C # 4 de Scott Dorman, y una de las cosas que me gustó fue que Scott eligió una ordenación bastante sensata para los temas, en lugar de comenzar con lo que realmente son temas bastante avanzados en la gestión de la memoria.

Otra parte de la razón es que algunas páginas en la documentación de MSDN enfatizan fuertemente las consideraciones de almacenamiento. Documentación de MSDN especialmente antigua que todavía se mantiene desde los primeros días. Gran parte de esa documentación tiene errores sutiles que nunca se han eliminado, y debe recordar que fue escrita en un momento particular de la historia y para un público en particular.

¿Por qué la pila vs el montón es importante para los desarrolladores (principiantes) de .NET?

En mi opinión, no lo hace. Lo que es mucho más importante de entender es cosas como:

  • ¿Cuál es la diferencia en la semántica de copia entre un tipo de referencia y un tipo de valor?
  • ¿Cómo se comporta un parámetro "ref int x"?
  • ¿Por qué los tipos de valor deben ser inmutables?

Y así.

Usted asigna cosas y simplemente funciona, ¿verdad?

Ese es el ideal.

Ahora, hay situaciones en las que sí importa. La recolección de basura es impresionante y relativamente económica, pero no es gratuita. Copiar pequeñas estructuras alrededor es relativamente económico, pero no es gratis. Hay escenarios de rendimiento realistas en los que debe equilibrar el costo de la presión de recopilación con el costo de la copia excesiva. En esos casos, es muy útil tener una sólida comprensión del tamaño, la ubicación y la vida útil real de toda la memoria relevante.

Del mismo modo, hay escenarios de interoperabilidad realistas en los que es necesario saber qué hay en la pila y qué hay en el montón, y qué podría estar moviendo el recolector de basura. Es por eso que C # tiene características como "fijo", "stackalloc", etc.

Pero esos son todos escenarios avanzados. Idealmente, un programador principiante no debe preocuparse por nada de esto.

Eric Lippert
fuente
2
Gracias por la respuesta Eric. Sus publicaciones de blog recientes sobre el tema son lo que realmente me impulsó a publicar la pregunta.
Greg
13

Todos ustedes están perdiendo el punto. La razón por la cual la distinción pila / montón es importante es por el alcance .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

Una vez que x sale del alcance, el objeto que se creó desaparece categóricamente . Eso es solo porque está asignado en la pila, no en el montón. No hay nada que pueda haber entrado en la parte "..." del método que pueda cambiar ese hecho. En particular, cualquier asignación o llamada a un método solo podría haber hecho copias de la estructura S, no haber creado nuevas referencias para permitirle seguir viviendo.

class C { ... }

void f() {
     var x = new C();
     ...
}

Historia totalmente diferente! Dado que x está ahora en el montón , su objeto (es decir, el objeto en sí , no una copia del mismo) podría continuar viviendo después de que x salga del alcance. De hecho, la única forma en que no continuará viviendo es si x es la única referencia a ella. Si las asignaciones o llamadas a métodos en la parte "..." han creado otras referencias que aún están "activas" cuando x sale del alcance, entonces ese objeto continuará vivo.

Ese es un concepto muy importante, y la única forma de comprender realmente "qué y por qué" es saber la diferencia entre la asignación de pila y la pila.

JoelFan
fuente
No estoy seguro de haber visto ese argumento presentado antes en los libros junto con la discusión de pila / montón, pero es bueno. +1
Greg
2
Debido a la forma en que C # genera cierres, el código en el ...podría causar xque se convierta en un campo de una clase generada por el compilador y, por lo tanto, durar más que el alcance indicado. Personalmente, encuentro desagradable la idea de la elevación implícita, pero los diseñadores de lenguaje parecen ir por ella (en lugar de exigir que cualquier variable que se levante tenga algo en su declaración para especificar eso). Para garantizar la corrección del programa, a menudo es necesario tener en cuenta todas las referencias que puedan existir a un objeto. Saber que para cuando regrese una rutina, no será útil ninguna copia de una referencia pasada.
supercat
1
En cuanto a las 'estructuras están en la pila', la declaración correcta es que si una ubicación de almacenamiento se declara como structType foo, la ubicación de almacenamiento foocontiene el contenido de sus campos; si fooestá en la pila, también lo están sus campos. Si fooestá en el montón, también lo están sus campos. si fooestá en un Apple II en red, también lo están sus campos. Por el contrario, si foofuera un tipo de clase, contendría nullo una referencia a un objeto. La única situación en la que se foopodría decir que el tipo de clase contiene los campos del objeto sería si fuera el único campo de una clase y tuviera una referencia a sí mismo.
supercat
+1, me encanta tu idea aquí y creo que es válida ... Sin embargo, no creo que sea una justificación de por qué los libros cubren este tema con tanta profundidad. Parece que lo que explicaste aquí podría reemplazar esos 3 o 4 capítulos de dicho libro y SERÁ MUCHO más útil.
Frank V
1
por lo que sé, las estructuras no tienen que hacerlo, ni siempre van a la pila.
sara
5

En cuanto a POR QUÉ cubren el tema, estoy de acuerdo con @Kirk en que es un concepto importante, que debes entender. Cuanto mejor conozca los mecanismos, mejor podrá hacer para crear excelentes aplicaciones que funcionen sin problemas.

Ahora Eric Lippert parece estar de acuerdo con usted en que la mayoría de los autores no cubren correctamente el tema. Le recomiendo que lea su blog para lograr una gran comprensión de lo que hay debajo del capó.

Fede
fuente
2
Bueno, la publicación de Eric señala que todo lo que necesita saber son las características abiertas del valor y los tipos de referencia, y no debe esperar que la implementación se mantenga igual. Creo que es bastante cuestionador sugerir que hay una manera eficiente de volver a implementar C # sin una pila, pero su punto es correcto: no es parte de la especificación del lenguaje. Entonces, la única razón para usar esta explicación que se me ocurre es que es una alegoría útil para los programadores que conocen otros idiomas, particularmente C. Siempre y cuando sepan que es una alegoría, que mucha literatura no aclara.
Jeremy
5

Bueno, pensé que ese era el objetivo de los entornos administrados. Incluso iría tan lejos como llamar a esto un detalle de implementación del tiempo de ejecución subyacente sobre el que NO debe hacer ninguna suposición, ya que podría cambiar en cualquier momento.

No sé mucho sobre .NET, pero que yo sepa, está JITED antes de la ejecución. El JIT, por ejemplo, podría realizar un análisis de escape y, de lo contrario, de repente estarías teniendo objetos en la pila o simplemente en algunos registros. No puedes saber esto.

Supongo que algunos libros lo cubren simplemente porque los autores le atribuyen una gran importancia o porque asumen que su audiencia sí (p. Ej., Si escribió un "C # para programadores de C ++", probablemente debería cubrir el tema).

Sin embargo, creo que no hay mucho más que decir que "se gestiona la memoria". De lo contrario, la gente podría sacar conclusiones erróneas.

back2dos
fuente
2

Debe comprender cómo funciona la asignación de memoria para usarla de manera eficiente, incluso si no tiene que administrarla explícitamente. Esto se aplica a casi todas las abstracciones en informática.

Kirk Fernandes
fuente
2
En los lenguajes administrados, debe saber la diferencia entre un tipo de valor y un tipo de referencia, pero más allá de eso, es fácil envolverse alrededor del eje pensando en cómo se maneja debajo del capó. Vea aquí para ver un ejemplo: stackoverflow.com/questions/4083981/…
Robert Harvey
Debo estar de acuerdo con Robert
La diferencia entre el montón asignado y la pila asignada es exactamente lo que explica la diferencia entre el valor y los tipos de referencia.
Jeremy el
3
@ Jeremy: No realmente. Ver blogs.msdn.com/b/ericlippert/archive/2010/09/30/…
Robert Harvey
1
Jeremy, la diferencia entre la asignación del montón y la pila no puede explicar el comportamiento diferente entre los tipos de valor y los tipos de referencia, porque hay momentos en que los tipos de valor y los tipos de referencia están en el montón, y sin embargo se comportan de manera diferente. Lo más importante que debe entender es (por ejemplo) cuando necesita usar el paso por referencia para los tipos de referencia frente a los tipos de valor. Esto solo depende de "es un tipo de valor o tipo de referencia", no "está en el montón".
Tim Goodman el
2

Puede haber algunos casos extremos en los que eso puede marcar la diferencia. El espacio de pila predeterminado es 1meg mientras que el montón es de varios conciertos. Entonces, si su solución contiene una gran cantidad de objetos, puede quedarse sin espacio de pila mientras tiene mucho espacio de almacenamiento dinámico.

Sin embargo, en su mayor parte es bastante académico.

GruñónMono
fuente
Sí, pero dudo que alguno de estos libros se esfuerce por explicar que las referencias en sí están almacenadas en la pila, por lo que no importa si tiene muchos tipos de referencia o muchos tipos de valor, aún puede tener un desbordamiento de pila.
Jeremy
0

Como usted dice, se supone que C # abstrae la administración de la memoria, y la asignación de pila versus pila son detalles de implementación que, en teoría, el desarrollador no debería necesitar conocer.

El problema es que algunas cosas son realmente difíciles de explicar de forma intuitiva sin hacer referencia a estos detalles de implementación. Intente explicar el comportamiento observable cuando modifique los tipos de valores mutables; es casi imposible hacerlo sin hacer referencia a la distinción pila / montón. O trate de explicar por qué incluso tener tipos de valores en el idioma en primer lugar, y cuándo los usaría. Debe comprender la distinción para que el lenguaje tenga sentido.

Tenga en cuenta que los libros sobre digamos Python o JavaScript no hacen gran cosa si incluso lo mencionan. Esto se debe a que todo está asignado al montón o es inmutable, lo que significa que las diferentes semánticas de copia nunca entran en juego. En esos lenguajes funciona la abstracción de la memoria, en C # es permeable.

JacquesB
fuente