Cuando crea una instancia de una clase con el operador new
, la memoria se asigna en el montón. Cuando crea una instancia de una estructura con el operador new
, ¿dónde se asigna la memoria, en el montón o en la pila?¿El uso de "nuevo" en una estructura lo asigna en el montón o la pila?
Respuesta
Bien, veamos si puedo aclarar esto.
En primer lugar, Ash es correcto: la cuestión es no acerca de dónde tipo de valor las variables se asignan. Esa es una pregunta diferente, y una a la cual la respuesta no es solo "en la pila". Es más complicado que eso (y aún más complicado por C# 2). Tengo un article on the topic y ampliaré si así lo solicita, pero vamos a tratar solo con el operador new
.
En segundo lugar, todo esto realmente depende del nivel de lo que está hablando.Estoy mirando lo que el compilador hace con el código fuente, en términos de IL que crea. Es más que posible que el compilador JIT haga cosas inteligentes en términos de optimizar una gran cantidad de asignación "lógica".
En tercer lugar, estoy ignorando los genéricos, sobre todo porque realmente no sé la respuesta, y en parte porque complicaría demasiado las cosas.
Finalmente, todo esto es solo con la implementación actual. La especificación de C# no especifica gran parte de esto; se trata efectivamente de un detalle de implementación. Hay quienes creen que los desarrolladores de código administrado realmente no deberían preocuparse. No estoy seguro de haber llegado tan lejos, pero vale la pena imaginar un mundo en el que, de hecho, todas las variables locales vivan en el montón, lo que aún se ajustaría a las especificaciones.
Hay dos situaciones diferentes con el operador new
en los tipos de valor: puede llamar a un constructor sin parámetros (por ejemplo new Guid()
) o un constructor parameterful (por ejemplo new Guid(someString)
). Estos generan IL significativamente diferente. Para comprender por qué, debe comparar las especificaciones C# y CLI: de acuerdo con C#, todos los tipos de valores tienen un constructor sin parámetros. De acuerdo con la especificación CLI, no los tipos de valor tienen constructores sin parámetros. (Recupere los constructores de un tipo de valor con reflexión alguna vez; no encontrará uno sin parámetros).
Tiene sentido que C# trate el "inicializar un valor con ceros" como un constructor, porque mantiene el lenguaje consistente - puede pensar en new(...)
como siempre llamando a un constructor. Tiene sentido que la CLI piense de otra manera, ya que no hay un código real para llamar, y ciertamente no hay un código específico del tipo.
También hace la diferencia qué va a hacer con el valor después de haberlo inicializado. La IL utiliza para
Guid localVariable = new Guid(someString);
es diferente a la IL utilizado para:
myInstanceOrStaticVariable = new Guid(someString);
Además, si el valor se utiliza como un valor intermedio, por ejemplo, un argumento para una llamada a un método, las cosas son ligeramente diferentes nuevamente. Para mostrar todas estas diferencias, aquí hay un breve programa de prueba. No muestra la diferencia entre las variables estáticas y las variables de instancia: el IL diferiría entre stfld
y stsfld
, pero eso es todo.
using System;
public class Test
{
static Guid field;
static void Main() {}
static void MethodTakingGuid(Guid guid) {}
static void ParameterisedCtorAssignToField()
{
field = new Guid("");
}
static void ParameterisedCtorAssignToLocal()
{
Guid local = new Guid("");
// Force the value to be used
local.ToString();
}
static void ParameterisedCtorCallMethod()
{
MethodTakingGuid(new Guid(""));
}
static void ParameterlessCtorAssignToField()
{
field = new Guid();
}
static void ParameterlessCtorAssignToLocal()
{
Guid local = new Guid();
// Force the value to be used
local.ToString();
}
static void ParameterlessCtorCallMethod()
{
MethodTakingGuid(new Guid());
}
}
Aquí está la IL para la clase, excluidos los bits irrelevantes (como NOP):
.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
// Removed Test's constructor, Main, and MethodTakingGuid.
.method private hidebysig static void ParameterisedCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
L_0010: ret
}
.method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
{
.maxstack 2
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: ldstr ""
L_0008: call instance void [mscorlib]System.Guid::.ctor(string)
// Removed ToString() call
L_001c: ret
}
.method private hidebysig static void ParameterisedCtorCallMethod() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0011: ret
}
.method private hidebysig static void ParameterlessCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
L_0006: initobj [mscorlib]System.Guid
L_000c: ret
}
.method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
// Removed ToString() call
L_0017: ret
}
.method private hidebysig static void ParameterlessCtorCallMethod() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
L_0009: ldloc.0
L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0010: ret
}
.field private static valuetype [mscorlib]System.Guid field
}
Como se puede ver, hay un montón de diferentes instrucciones utilizadas para llamar al constructor:
newobj
: Asigna el valor en la pila, llama a un constructor con parámetros. Usado para valores intermedios, p. para la asignación a un campo o usar como un argumento de método.call instance
: utiliza una ubicación de almacenamiento ya asignada (ya sea en la pila o no). Esto se usa en el código anterior para asignar a una variable local. Si a la misma variable local se le asigna un valor varias veces usando varias llamadasnew
, solo inicializa los datos sobre el valor anterior - no asigna más espacio de pila cada vez.initobj
: utiliza una ubicación de almacenamiento ya asignada y simplemente borra los datos. Esto se usa para todas nuestras llamadas de constructor sin parámetros, incluidas las que se asignan a una variable local. Para la llamada al método, una variable local intermedia se introduce efectivamente y su valor borrado porinitobj
.
Espero que esto muestre cuán complicado es el tema, al mismo tiempo que arroja un poco de luz sobre él. En algunos sentidos conceptuales, cada llamada a new
asigna espacio en la pila, pero como hemos visto, eso no es lo que realmente ocurre incluso en el nivel IL. Me gustaría destacar un caso en particular. Tome este método:
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
que "lógicamente" ha 4 asignaciones de pila - uno para la variable, y uno para cada uno de los tres new
llamadas - pero de hecho (para ese código específico) la pila es única asignada una vez , y luego se reutiliza la misma ubicación de almacenamiento.
EDITAR: Para que quede claro, esto solo es cierto en algunos casos ... en particular, el valor de guid
no estará visible si el constructor Guid
arroja una excepción, por lo que el compilador C# puede reutiliza la misma ranura de pila Vea el blog post on value type construction de Eric Lippert para más detalles y un caso donde no aplica.
He aprendido mucho al escribir esta respuesta: solicite una aclaración si algo no está claro.
Como en todos los tipos de valores, las estructuras siempre van donde estaban declaradas.
Consulte esta pregunta here para obtener más información sobre cuándo utilizar las estructuras. Y esta pregunta here para obtener más información sobre las estructuras.
Editar: tuve mistankely respondieron que SIEMPRE van en la pila. Esto es incorrect.
"estructuras siempre van donde fueron declarados", esto es un poco engañoso confuso. Un campo de estructura en una clase siempre se coloca en "memoria dinámica cuando se construye una instancia del tipo" - Jeff Richter. Esto puede ser indirectamente en el montón, pero no es lo mismo que un tipo de referencia normal en absoluto. – Ash
No, creo que es * exactamente * correcto, aunque no es lo mismo que un tipo de referencia. El valor de una variable vive donde se declara. El valor de una variable de tipo de referencia es una referencia, en lugar de los datos reales, eso es todo. –
En resumen, cada vez que crea (declara) un tipo de valor en cualquier parte de un método, siempre se crea en la pila. – Ash
Las estructuras se asignan a la pila. He aquí una explicación útil:
Además, las clases cuando se crea una instancia dentro de .NET asignar memoria en el montón o espacio de memoria reservado de .NET. Mientras que las estructuras producen más eficiencia cuando se crean instancias debido a la asignación en la pila. Además, debe tenerse en cuenta que los parámetros de paso dentro de las estructuras se realizan por valor.
Esto no cubre el caso cuando una estructura es parte de una clase, en ese punto vive en el montón, con el resto de los datos del objeto. –
Sí, pero en realidad se centra y responde la pregunta que se hace. Votado arriba. – Ash
... siendo incorrecto y engañoso. Lo sentimos, pero no hay respuestas cortas a esta pregunta: Jeffrey's es la única respuesta completa. –
La memoria que contiene los campos de una estructura se puede asignar a la pila o al montón, según las circunstancias. Si la variable struct-type es una variable local o un parámetro que no es capturado por un delegado o clase de iterador anónimo, entonces se asignará en la pila. Si la variable es parte de alguna clase, se asignará dentro de la clase en el montón.
Si la estructura se asigna en el montón, llamar al nuevo operador no es realmente necesario para asignar la memoria. El único propósito sería establecer los valores de campo según lo que esté en el constructor. Si no se llama al constructor, todos los campos obtendrán sus valores predeterminados (0 o nulo).
De forma similar para las estructuras asignadas en la pila, excepto que C# requiere que todas las variables locales se establezcan en algún valor antes de su uso, por lo que debe llamar un constructor personalizado o el constructor predeterminado (un constructor que no toma parámetros siempre está disponible para las estructuras).
Casi todas las estructuras que se consideran tipos de valores, se asignan en la pila, mientras que los objetos se asignan en el montón, mientras que la referencia del objeto (puntero) se asigna en la pila.
Para decirlo de forma compacta, nuevo es un nombre inapropiado para las estructuras, llamando a las nuevas simplemente llama al constructor. La única ubicación de almacenamiento para la estructura es la ubicación en la que está definida.
Si es una variable miembro, se almacena directamente en lo que se define, si se trata de una variable local o parámetro, se almacena en la pila.
Contraste esto con las clases, que tienen una referencia donde la estructura se hubiera almacenado en su totalidad, mientras que los puntos de referencia en algún lugar en el montón. (Miembro dentro, local/parameter en la pila)
Puede ser útil mirar un poco en C++, donde no hay una distinción real entre clase/estructura. (Hay nombres similares en el idioma, pero solo se refieren a la accesibilidad predeterminada de las cosas) Cuando llamas a nuevo, obtienes un puntero a la ubicación del montón, mientras que si tienes una referencia sin puntero, se almacena directamente en la pila o dentro del otro objeto, ala construye en C#.
Probablemente me falta algo aquí, pero ¿por qué nos preocupa la asignación?
Los tipos de valores se pasan por valor;) y, por lo tanto, no se pueden mutar en un ámbito diferente de donde se definen. Para poder modificar el valor, debe agregar la palabra clave [ref].
Los tipos de referencia se pasan por referencia y pueden mutarse.
Por supuesto, las cadenas de tipos de referencia inmutables son las más populares.
diseño Array/inicialización: tipos de valor -> memoria cero [nombre, código postal] [nombre, zip] Tipos de referencia -> memoria cero -> null [ref] [ref]
Los tipos de referencia no se pasan por referencia; las referencias se pasan por valor. Eso es muy diferente. –
Un class
o struct
La declaración es como un modelo que se usa para crear instancias u objetos en tiempo de ejecución. Si define un class
o struct
llamado Persona, Persona es el nombre del tipo. Si declara e inicializa una variable p de tipo Persona, p se dice que es un objeto o instancia de Persona. Se pueden crear varias instancias del mismo tipo de persona, y cada instancia puede tener valores diferentes en su properties
y fields
.
A class
es un tipo de referencia. Cuando se crea un objeto del class
, la variable a la que está asignado el objeto solo contiene una referencia a esa memoria. Cuando la referencia del objeto se asigna a una nueva variable, la nueva variable se refiere al objeto original. Los cambios realizados a través de una variable se reflejan en la otra variable porque ambos se refieren a los mismos datos.
A struct
es un tipo de valor. Cuando se crea un struct
, la variable a la que está asignado el struct
contiene los datos reales de la estructura. Cuando el struct
se asigna a una nueva variable, se copia. La nueva variable y la variable original por lo tanto contienen dos copias separadas de los mismos datos. Los cambios realizados en una copia no afectan la otra copia.
En general, classes
se utilizan para modelar comportamientos más complejos o datos que se pretende modificar después de crear un objeto class
. Structs
son los más adecuados para estructuras de datos pequeñas que contienen principalmente datos que no se pretende modificar después de crear el struct
.
- 1. ¿Dónde se asigna la referencia de variable, en pila o en el montón?
- 2. son NSStrings almacenados en el montón o en la pila y lo que es una buena manera de inicializar una
- 3. ¿dónde se asigna la pila de Java?
- 4. ¿Por qué la asignación de memoria en el montón MUCHO más lento que en la pila?
- 5. contenedores STL en la pila y el montón
- 6. ¿Cómo crear una estructura en la pila en C?
- 7. Campos de clase, ¿están almacenados en la pila o en el montón?
- 8. ¿Cómo creo una matriz en C++ que está en el montón en lugar de la pila?
- 9. ¿Por qué un POD en una estructura inicializa cero mediante un constructor implícito al crear un objeto en el montón o un objeto temporal en la pila?
- 10. ¿Las primitivas de Java van en la pila o en el montón?
- 11. ¿Dónde reside el conjunto de constantes de String de Java, el montón o la pila?
- 12. ¿El montón es realmente un montón?
- 13. ¿Por qué querrías asignar memoria en el montón en lugar de la pila?
- 14. nuevo en la pila en lugar de montón (como alloca vs malloc)
- 15. ¿Dónde asigna CUDA el marco de pila para los granos?
- 16. Inicialización de la matriz en el montón
- 17. Destino de asignación de std :: aligned_storage (¿pila o montón?)
- 18. nuevo operador para asignación de memoria en el montón
- 19. ¿Qué se almacena en el montón y qué se almacena en la pila?
- 20. uso de pila libGL
- 21. ¿Qué ocurre cuando se asigna una nueva estructura a la matriz en C#?
- 22. Cómo ver el montón y el uso de una función en c usando valgrind?
- 23. Creación de matriz de objetos en la pila y montón
- 24. Los tipos de referencia viven en el montón, los tipos de valor viven en la pila
- 25. C++ error de la herencia cuando el objeto se le asigna en la pila
- 26. ¿Qué sucede cuando intenta liberar memoria asignada por el administrador de montón, que asigna más de lo que se solicita?
- 27. pila y montón en V8 (JavaScript)
- 28. Objetos asignados en el montón
- 29. ¿Por qué la memoria se divide en pila y montón?
- 30. patrones de ubicación de memoria en la pila y en el montón
Buena respuesta Jon, la aclaración de la complejidad de esta área es muy importante. Debo saber que a estas alturas rara vez hay una respuesta en blanco y negro para la mayoría de las preguntas de desarrollo de software. – Ash
Jon, el código de ejemplo HowManyStackAllocations es bueno. Pero, ¿podría cambiarlo para usar un Struct en lugar de Guid o agregar un nuevo ejemplo de Struct? Creo que entonces abordaría directamente la pregunta original de @ kedar. – Ash
Guid ya es una estructura. Consulte http://msdn.microsoft.com/en-us/library/system.guid.aspx No habría elegido un tipo de referencia para esta pregunta :) –